1use std::collections::hash_map;
2
3use anyhow::Result;
4use tycho_types::cell::{CellTreeStats, Lazy};
5use tycho_types::error::Error;
6use tycho_types::models::{
7 AccountState, AccountStatus, AccountStatusChange, ActionPhase, ChangeLibraryMode,
8 CurrencyCollection, ExecutedComputePhase, ExtraCurrencyCollection, LibRef, OutAction,
9 OwnedMessage, OwnedRelaxedMessage, RelaxedMsgInfo, ReserveCurrencyFlags, SendMsgFlags,
10 SimpleLib, StateInit, StorageUsedShort,
11};
12use tycho_types::num::{Tokens, VarUint56};
13use tycho_types::prelude::*;
14
15use crate::phase::receive::ReceivedMessage;
16use crate::util::{
17 ExtStorageStat, StateLimitsResult, StorageStatLimits, check_rewrite_dst_addr,
18 check_rewrite_src_addr, check_state_limits, check_state_limits_diff,
19};
20use crate::{ExecutorInspector, ExecutorState, PublicLibraryChange};
21
22pub struct ActionPhaseContext<'a, 'e> {
24 pub received_message: Option<&'a mut ReceivedMessage>,
26 pub original_balance: CurrencyCollection,
28 pub new_state: StateInit,
30 pub actions: Cell,
32 pub compute_phase: &'a ExecutedComputePhase,
34 pub inspector: Option<&'a mut ExecutorInspector<'e>>,
36}
37
38#[derive(Debug)]
40pub struct ActionPhaseFull {
41 pub action_phase: ActionPhase,
43 pub action_fine: Tokens,
45 pub state_exceeds_limits: bool,
47 pub bounce: bool,
49}
50
51impl ExecutorState<'_> {
52 pub fn action_phase(&mut self, mut ctx: ActionPhaseContext<'_, '_>) -> Result<ActionPhaseFull> {
53 const MAX_ACTIONS: u16 = 255;
54
55 let mut res = ActionPhaseFull {
56 action_phase: ActionPhase {
57 success: false,
58 valid: false,
59 no_funds: false,
60 status_change: AccountStatusChange::Unchanged,
61 total_fwd_fees: None,
62 total_action_fees: None,
63 result_code: -1,
64 result_arg: None,
65 total_actions: 0,
66 special_actions: 0,
67 skipped_actions: 0,
68 messages_created: 0,
69 action_list_hash: *ctx.actions.repr_hash(),
70 total_message_size: StorageUsedShort::ZERO,
71 },
72 action_fine: Tokens::ZERO,
73 state_exceeds_limits: false,
74 bounce: false,
75 };
76
77 let mut action_idx = 0u16;
79
80 let mut list = Vec::new();
81 let mut actions = ctx.actions.as_ref();
82 loop {
83 if actions.is_exotic() {
84 res.action_phase.result_code = ResultCode::ActionListInvalid as i32;
86 res.action_phase.result_arg = Some(action_idx as _);
87 res.action_phase.valid = false;
88 return Ok(res);
89 }
90
91 let mut cs = actions.as_slice_allow_exotic();
93 if cs.is_empty() {
94 break;
96 }
97
98 list.push(actions);
99
100 actions = match cs.load_reference() {
101 Ok(child) => child,
102 Err(_) => {
103 res.action_phase.result_code = ResultCode::ActionListInvalid as i32;
105 res.action_phase.result_arg = Some(action_idx as _);
106 res.action_phase.valid = false;
107 return Ok(res);
108 }
109 };
110
111 action_idx += 1;
112 if action_idx > MAX_ACTIONS {
113 res.action_phase.result_code = ResultCode::TooManyActions as i32;
115 res.action_phase.result_arg = Some(action_idx as _);
116 res.action_phase.valid = false;
117 return Ok(res);
118 }
119 }
120
121 res.action_phase.total_actions = action_idx;
122
123 let mut parsed_list = Vec::with_capacity(list.len());
125 for (action_idx, item) in list.into_iter().rev().enumerate() {
126 let mut cs = item.as_slice_allow_exotic();
127 cs.load_reference().ok(); let mut cs_parsed = cs;
131 if let Ok(item) = OutAction::load_from(&mut cs_parsed)
132 && cs_parsed.is_empty()
133 {
134 parsed_list.push(Some(item));
136 continue;
137 }
138
139 if cs.size_bits() >= 40 && cs.load_u32()? == OutAction::TAG_SEND_MSG {
141 let mode = SendMsgFlags::from_bits_retain(cs.load_u8()?);
142 if mode.contains(SendMsgFlags::IGNORE_ERROR) {
143 res.action_phase.skipped_actions += 1;
145 parsed_list.push(None);
146 continue;
147 } else if mode.contains(SendMsgFlags::BOUNCE_ON_ERROR) {
148 res.bounce = true;
151 }
152 }
153
154 res.action_phase.result_code = ResultCode::ActionInvalid as i32;
155 res.action_phase.result_arg = Some(action_idx as _);
156 res.action_phase.valid = false;
157 return Ok(res);
158 }
159
160 res.action_phase.valid = true;
162
163 let original_libs = ctx.new_state.libraries.clone();
165
166 let mut action_ctx = ActionContext {
168 need_bounce_on_fail: false,
169 strict_extra_currency: self.params.strict_extra_currency,
170 received_message: ctx.received_message,
171 original_balance: &ctx.original_balance,
172 remaining_balance: self.balance.clone(),
173 reserved_balance: CurrencyCollection::ZERO,
174 action_fine: &mut res.action_fine,
175 new_state: &mut ctx.new_state,
176 end_lt: self.end_lt,
177 out_msgs: Vec::new(),
178 delete_account: false,
179 public_libs_diff: ctx.inspector.is_some().then(Default::default),
180 compute_phase: ctx.compute_phase,
181 action_phase: &mut res.action_phase,
182 };
183
184 for (action_idx, action) in parsed_list.into_iter().enumerate() {
185 let Some(action) = action else {
186 continue;
187 };
188
189 action_ctx.need_bounce_on_fail = false;
190 action_ctx.action_phase.result_code = -1;
191 action_ctx.action_phase.result_arg = Some(action_idx as _);
192
193 let action = match action {
194 OutAction::SendMsg { mode, out_msg } => {
195 let mut rewrite = None;
196 loop {
197 match self.do_send_message(mode, &out_msg, &mut action_ctx, rewrite) {
198 Ok(SendMsgResult::Sent) => break Ok(()),
199 Ok(SendMsgResult::Rewrite(r)) => rewrite = Some(r),
200 Err(e) => break Err(e),
201 }
202 }
203 }
204 OutAction::SetCode { new_code } => self.do_set_code(new_code, &mut action_ctx),
205 OutAction::ReserveCurrency { mode, value } => {
206 self.do_reserve_currency(mode, value, &mut action_ctx)
207 }
208 OutAction::ChangeLibrary { mode, lib } => {
209 self.do_change_library(mode, lib, &mut action_ctx)
210 }
211 };
212
213 if let Err(ActionFailed) = action {
214 let result_code = &mut action_ctx.action_phase.result_code;
215 if *result_code == -1 {
216 *result_code = ResultCode::ActionInvalid as i32;
217 }
218 if *result_code == ResultCode::NotEnoughBalance as i32
219 || *result_code == ResultCode::NotEnoughExtraBalance as i32
220 {
221 action_ctx.action_phase.no_funds = true;
222 }
223
224 action_ctx.apply_fine_on_error(
231 &mut self.balance,
232 &mut self.total_fees,
233 self.params.charge_action_fees_on_fail,
234 )?;
235
236 res.bounce |= action_ctx.need_bounce_on_fail;
238
239 return Ok(res);
241 }
242 }
243
244 let is_masterchain = self.address.is_masterchain();
245
246 if !self.is_special {
249 let limits = &self.config.size_limits;
250 let check = match &self.state {
251 AccountState::Active(current_state) => check_state_limits_diff(
252 current_state,
253 action_ctx.new_state,
254 limits,
255 is_masterchain,
256 &mut self.cached_storage_stat,
257 ),
258 AccountState::Uninit | AccountState::Frozen(_) => check_state_limits(
259 action_ctx.new_state.code.as_ref(),
260 action_ctx.new_state.data.as_ref(),
261 &action_ctx.new_state.libraries,
262 limits,
263 is_masterchain,
264 &mut self.cached_storage_stat,
265 ),
266 };
267
268 if matches!(check, StateLimitsResult::Exceeds) {
269 action_ctx.apply_fine_on_error(
271 &mut self.balance,
272 &mut self.total_fees,
273 self.params.charge_action_fees_on_fail,
274 )?;
275
276 res.bounce |= action_ctx.need_bounce_on_fail;
278 res.action_phase.result_code = ResultCode::StateOutOfLimits as i32;
279 res.state_exceeds_limits = true;
280 return Ok(res);
281 }
282
283 }
287
288 if !action_ctx.action_fine.is_zero() {
289 action_ctx
290 .action_phase
291 .total_action_fees
292 .get_or_insert_default()
293 .try_add_assign(*action_ctx.action_fine)?;
294 }
295
296 action_ctx
297 .remaining_balance
298 .try_add_assign(&action_ctx.reserved_balance)?;
299
300 action_ctx.action_phase.result_code = 0;
301 action_ctx.action_phase.result_arg = None;
302 action_ctx.action_phase.success = true;
303
304 enum ApplyDiff {
305 Skip,
306 FromActions,
307 Clear,
308 }
309
310 let mut apply_diff = if self.end_status == AccountStatus::Active {
311 ApplyDiff::FromActions
313 } else {
314 ApplyDiff::Skip
316 };
317
318 if action_ctx.delete_account {
319 if self.params.strict_extra_currency {
320 debug_assert!(action_ctx.remaining_balance.tokens.is_zero());
322 } else {
323 debug_assert!(action_ctx.remaining_balance.is_zero());
325 }
326 action_ctx.action_phase.status_change = AccountStatusChange::Deleted;
327
328 if let ApplyDiff::FromActions = apply_diff {
331 apply_diff = ApplyDiff::Clear;
332 }
333
334 self.end_status = if action_ctx.remaining_balance.is_zero() {
335 AccountStatus::NotExists
338 } else {
339 AccountStatus::Uninit
341 };
342 self.cached_storage_stat = None;
343 };
344
345 if let Some(fees) = action_ctx.action_phase.total_action_fees {
346 self.total_fees.try_add_assign(fees)?;
348 }
349 self.balance = action_ctx.remaining_balance;
350
351 if is_masterchain
352 && let Some(inspector) = ctx.inspector
353 && let Some(diff) = action_ctx.public_libs_diff
354 {
355 match apply_diff {
356 ApplyDiff::Skip => {}
357 ApplyDiff::Clear => {
358 for entry in original_libs.values() {
359 let lib = entry?;
360 if lib.public {
361 match inspector.public_libs_diff.entry(*lib.root.repr_hash()) {
362 hash_map::Entry::Vacant(entry) => {
363 entry.insert(PublicLibraryChange::Remove);
364 }
365 hash_map::Entry::Occupied(entry) => match entry.get() {
366 PublicLibraryChange::Add(_) => {
367 entry.remove();
370 }
371 PublicLibraryChange::Remove => {}
373 },
374 }
375 }
376 }
377 }
378 ApplyDiff::FromActions => {
379 for (key, lib) in diff {
380 let change = lib.change;
381 match inspector.public_libs_diff.entry(key) {
382 hash_map::Entry::Vacant(entry) => {
383 entry.insert(change);
384 }
385 hash_map::Entry::Occupied(mut entry) => match (&entry.get(), &change) {
386 (PublicLibraryChange::Add(_), PublicLibraryChange::Remove) => {
388 entry.remove();
389 }
390 _ => {
391 entry.insert(change);
392 }
393 },
394 }
395 }
396 }
397 }
398 }
399
400 self.out_msgs = action_ctx.out_msgs;
401 self.end_lt = action_ctx.end_lt;
402 self.state = AccountState::Active(ctx.new_state);
403
404 Ok(res)
405 }
406
407 fn do_send_message(
409 &self,
410 mode: SendMsgFlags,
411 out_msg: &Lazy<OwnedRelaxedMessage>,
412 ctx: &mut ActionContext<'_>,
413 mut rewrite: Option<MessageRewrite>,
414 ) -> Result<SendMsgResult, ActionFailed> {
415 const MASK: u8 = SendMsgFlags::all().bits();
416 const INVALID_MASK: SendMsgFlags =
417 SendMsgFlags::ALL_BALANCE.union(SendMsgFlags::WITH_REMAINING_BALANCE);
418 const EXT_MSG_MASK: u8 = SendMsgFlags::PAY_FEE_SEPARATELY
419 .union(SendMsgFlags::IGNORE_ERROR)
420 .union(SendMsgFlags::BOUNCE_ON_ERROR)
421 .bits();
422 const DELETE_MASK: SendMsgFlags =
423 SendMsgFlags::ALL_BALANCE.union(SendMsgFlags::DELETE_IF_EMPTY);
424
425 if mode.contains(SendMsgFlags::BOUNCE_ON_ERROR) {
427 ctx.need_bounce_on_fail = true;
428 }
429
430 let skip_invalid = mode.contains(SendMsgFlags::IGNORE_ERROR);
432 let check_skip_invalid = |e: ResultCode, ctx: &mut ActionContext<'_>| {
433 if skip_invalid {
434 ctx.action_phase.skipped_actions += 1;
435 Ok(SendMsgResult::Sent)
436 } else {
437 ctx.action_phase.result_code = e as i32;
438 Err(ActionFailed)
439 }
440 };
441
442 if mode.bits() & !MASK != 0 || mode.contains(INVALID_MASK) {
443 return check_skip_invalid(ResultCode::ActionInvalid, ctx);
446 }
447
448 if out_msg.is_exotic() {
450 return Err(ActionFailed);
451 }
452
453 let mut relaxed_info;
455 let mut state_init_cs;
456 let mut body_cs;
457
458 {
459 let mut cs = out_msg.as_slice_allow_exotic();
460
461 relaxed_info = RelaxedMsgInfo::load_from(&mut cs)?;
462 state_init_cs = match load_state_init_as_slice(&mut cs, rewrite.is_none()) {
464 Ok(cs) => cs,
465 Err(LoadStateInitError::BadStateInit) => {
467 return check_skip_invalid(ResultCode::ActionInvalid, ctx);
468 }
469 Err(LoadStateInitError::Other) => return Err(ActionFailed),
471 };
472 body_cs = load_body_as_slice(&mut cs)?;
473
474 if !cs.is_empty() {
475 return Err(ActionFailed);
477 }
478 }
479
480 let rewritten_state_init_cb;
482 if rewrite.is_some() {
483 if state_init_cs.size_refs() >= 2 {
484 rewritten_state_init_cb = rewrite_state_init_to_cell(state_init_cs);
486 state_init_cs = rewritten_state_init_cb.as_full_slice();
487 } else {
488 rewrite = Some(MessageRewrite::BodyToCell);
490 }
491 }
492
493 let rewritten_body_cs;
494 if let Some(MessageRewrite::BodyToCell) = rewrite
495 && body_cs.size_bits() > 1
496 && !body_cs.get_bit(0).unwrap()
497 {
498 rewritten_body_cs = rewrite_body_to_cell(body_cs);
500 body_cs = rewritten_body_cs.as_full_slice();
501 }
502
503 let mut use_mc_prices = self.address.is_masterchain();
505 match &mut relaxed_info {
506 RelaxedMsgInfo::Int(info) => {
508 if !check_rewrite_src_addr(&self.address, &mut info.src) {
510 return check_skip_invalid(ResultCode::InvalidSrcAddr, ctx);
511 };
512
513 if !check_rewrite_dst_addr(&self.config.workchains, &mut info.dst) {
515 return check_skip_invalid(ResultCode::InvalidDstAddr, ctx);
516 }
517 use_mc_prices |= info.dst.is_masterchain();
518
519 if self.params.strict_extra_currency {
521 match normalize_extra_balance(
522 std::mem::take(&mut info.value.other),
523 MAX_MSG_EXTRA_CURRENCIES,
524 ) {
525 Ok(other) => info.value.other = other,
526 Err(BalanceExtraError::InvalidDict(_)) => {
527 return check_skip_invalid(ResultCode::NotEnoughBalance, ctx);
528 }
529 Err(BalanceExtraError::OutOfLimit) => {
530 return check_skip_invalid(ResultCode::TooManyExtraCurrencies, ctx);
531 }
532 }
533 }
534
535 info.fwd_fee = Tokens::ZERO;
537
538 info.created_at = self.params.block_unixtime;
540 info.created_lt = ctx.end_lt;
541
542 info.ihr_disabled = true;
544 info.bounced = false;
545 }
546 RelaxedMsgInfo::ExtOut(info) => {
548 if mode.bits() & !EXT_MSG_MASK != 0 {
549 return check_skip_invalid(ResultCode::ActionInvalid, ctx);
551 }
552
553 if !check_rewrite_src_addr(&self.address, &mut info.src) {
555 return check_skip_invalid(ResultCode::InvalidSrcAddr, ctx);
556 }
557
558 info.created_at = self.params.block_unixtime;
560 info.created_lt = ctx.end_lt;
561 }
562 };
563
564 let prices = self.config.fwd_prices(use_mc_prices);
566 let mut max_cell_count = self.config.size_limits.max_msg_cells;
567 let fine_per_cell;
568 if self.is_special {
569 fine_per_cell = 0;
570 } else {
571 fine_per_cell = (prices.cell_price >> 16) / 4;
572
573 let mut funds = ctx.remaining_balance.tokens;
574 if let RelaxedMsgInfo::Int(info) = &relaxed_info
575 && !mode.contains(SendMsgFlags::ALL_BALANCE)
576 && !mode.contains(SendMsgFlags::PAY_FEE_SEPARATELY)
577 {
578 let mut new_funds = info.value.tokens;
579
580 if mode.contains(SendMsgFlags::WITH_REMAINING_BALANCE)
581 && (|| {
582 let msg_balance_remaining = match &ctx.received_message {
583 Some(msg) => msg.balance_remaining.tokens,
584 None => Tokens::ZERO,
585 };
586 new_funds.try_add_assign(msg_balance_remaining)?;
587 new_funds.try_sub_assign(ctx.compute_phase.gas_fees)?;
588 new_funds.try_sub_assign(*ctx.action_fine)?;
589
590 Ok::<_, tycho_types::error::Error>(())
591 })()
592 .is_err()
593 {
594 return check_skip_invalid(ResultCode::NotEnoughBalance, ctx);
595 }
596
597 funds = std::cmp::min(funds, new_funds);
598 }
599
600 if funds < Tokens::new(max_cell_count as u128 * fine_per_cell as u128) {
601 debug_assert_ne!(fine_per_cell, 0);
602 max_cell_count = (funds.into_inner() / fine_per_cell as u128)
603 .try_into()
604 .unwrap_or(u32::MAX);
605 }
606 }
607
608 let collect_fine = |cells: u32, ctx: &mut ActionContext<'_>| {
609 let mut fine = Tokens::new(
610 fine_per_cell.saturating_mul(std::cmp::min(max_cell_count, cells) as u64) as _,
611 );
612 fine = std::cmp::min(fine, ctx.remaining_balance.tokens);
613 ctx.action_fine.try_add_assign(fine)?;
614 ctx.remaining_balance.try_sub_assign_tokens(fine)
615 };
616
617 let stats = 'stats: {
619 let mut stats = ExtStorageStat::with_limits(StorageStatLimits {
620 bit_count: self.config.size_limits.max_msg_bits,
621 cell_count: max_cell_count,
622 });
623
624 'valid: {
625 for cell in state_init_cs.references() {
626 if !stats.add_cell(cell) {
627 break 'valid;
628 }
629 }
630
631 for cell in body_cs.references() {
632 if !stats.add_cell(cell) {
633 break 'valid;
634 }
635 }
636
637 if !self.params.strict_extra_currency
639 && let RelaxedMsgInfo::Int(int) = &relaxed_info
640 && let Some(cell) = int.value.other.as_dict().root()
641 && !stats.add_cell(cell.as_ref())
642 {
643 break 'valid;
644 }
645
646 break 'stats stats.stats();
647 }
648
649 collect_fine(stats.cells, ctx)?;
650 return check_skip_invalid(ResultCode::MessageOutOfLimits, ctx);
651 };
652
653 let check_skip_invalid = move |e: ResultCode, ctx: &mut ActionContext<'_>| {
655 collect_fine(stats.cell_count as _, ctx)?;
656 check_skip_invalid(e, ctx)
657 };
658
659 let fwd_fee = if self.is_special {
661 Tokens::ZERO
662 } else {
663 prices.compute_fwd_fee(stats)
664 };
665
666 let msg;
668 let fees_collected;
669 match &mut relaxed_info {
670 RelaxedMsgInfo::Int(info) => {
671 let value_to_pay = match ctx.rewrite_message_value(&mut info.value, mode, fwd_fee) {
673 Ok(total_value) => total_value,
674 Err(_) => return check_skip_invalid(ResultCode::NotEnoughBalance, ctx),
675 };
676
677 if ctx.remaining_balance.tokens < value_to_pay {
679 return check_skip_invalid(ResultCode::NotEnoughBalance, ctx);
680 }
681
682 if self.params.strict_extra_currency
684 && !check_extra_balance(&info.value.other, MAX_MSG_EXTRA_CURRENCIES)
685 {
686 return check_skip_invalid(ResultCode::TooManyExtraCurrencies, ctx);
687 }
688
689 if self.params.authority_marks_enabled
691 && !self.is_marks_authority
692 && let Some(marks) = &self.config.authority_marks
693 && marks.has_authority_marks_in(&info.value)?
694 {
695 return check_skip_invalid(ResultCode::NotEnoughExtraBalance, ctx);
696 }
697
698 let other = match ctx.remaining_balance.other.checked_sub(&info.value.other) {
700 Ok(other) => other,
701 Err(_) => return check_skip_invalid(ResultCode::NotEnoughExtraBalance, ctx),
702 };
703
704 fees_collected = prices.get_first_part(fwd_fee);
706 info.fwd_fee = fwd_fee - fees_collected;
707
708 msg = match build_message(&relaxed_info, &state_init_cs, &body_cs) {
710 Ok(msg) => msg,
711 Err(_) => match MessageRewrite::next(rewrite) {
712 Some(rewrite) => return Ok(SendMsgResult::Rewrite(rewrite)),
713 None => return check_skip_invalid(ResultCode::FailedToFitMessage, ctx),
714 },
715 };
716
717 if let Some(msg) = &mut ctx.received_message
719 && (mode.contains(SendMsgFlags::ALL_BALANCE)
720 || mode.contains(SendMsgFlags::WITH_REMAINING_BALANCE))
721 {
722 if self.params.strict_extra_currency {
723 msg.balance_remaining.tokens = Tokens::ZERO;
725 } else {
726 msg.balance_remaining = CurrencyCollection::ZERO;
728 }
729 }
730
731 ctx.remaining_balance.tokens -= value_to_pay;
733 ctx.remaining_balance.other = other;
734 }
735 RelaxedMsgInfo::ExtOut(_) => {
736 if ctx.remaining_balance.tokens < fwd_fee {
738 return check_skip_invalid(ResultCode::NotEnoughBalance, ctx);
739 }
740
741 msg = match build_message(&relaxed_info, &state_init_cs, &body_cs) {
743 Ok(msg) => msg,
744 Err(_) => match MessageRewrite::next(rewrite) {
745 Some(rewrite) => return Ok(SendMsgResult::Rewrite(rewrite)),
746 None => return check_skip_invalid(ResultCode::FailedToFitMessage, ctx),
747 },
748 };
749
750 ctx.remaining_balance.tokens -= fwd_fee;
752 fees_collected = fwd_fee;
753 }
754 }
755
756 update_total_msg_stat(
757 &mut ctx.action_phase.total_message_size,
758 stats,
759 msg.bit_len(),
760 );
761
762 ctx.action_phase.messages_created += 1;
763 ctx.end_lt += 1;
764
765 ctx.out_msgs.push(msg);
766
767 *ctx.action_phase.total_action_fees.get_or_insert_default() += fees_collected;
768 *ctx.action_phase.total_fwd_fees.get_or_insert_default() += fwd_fee;
769
770 if mode.contains(DELETE_MASK) {
771 ctx.delete_account = if self.params.strict_extra_currency {
772 debug_assert!(ctx.remaining_balance.tokens.is_zero());
774 ctx.reserved_balance.tokens.is_zero()
775 } else {
776 debug_assert!(ctx.remaining_balance.is_zero());
778 ctx.reserved_balance.is_zero()
779 };
780 }
781
782 Ok(SendMsgResult::Sent)
783 }
784
785 fn do_set_code(&self, new_code: Cell, ctx: &mut ActionContext<'_>) -> Result<(), ActionFailed> {
787 ctx.new_state.code = Some(new_code);
789 ctx.action_phase.special_actions += 1;
790
791 Ok(())
793 }
794
795 fn do_reserve_currency(
797 &self,
798 mode: ReserveCurrencyFlags,
799 mut reserve: CurrencyCollection,
800 ctx: &mut ActionContext<'_>,
801 ) -> Result<(), ActionFailed> {
802 const MASK: u8 = ReserveCurrencyFlags::all().bits();
803
804 if mode.contains(ReserveCurrencyFlags::BOUNCE_ON_ERROR) {
806 ctx.need_bounce_on_fail = true;
807 }
808
809 if mode.bits() & !MASK != 0 {
810 return Err(ActionFailed);
812 }
813
814 if self.params.strict_extra_currency && !reserve.other.is_empty() {
815 return Err(ActionFailed);
817 }
818
819 if mode.contains(ReserveCurrencyFlags::WITH_ORIGINAL_BALANCE) {
820 if mode.contains(ReserveCurrencyFlags::REVERSE) {
821 if self.params.strict_extra_currency {
822 reserve.tokens = ctx
823 .original_balance
824 .tokens
825 .checked_sub(reserve.tokens)
826 .ok_or(ActionFailed)?;
827 } else {
828 reserve = ctx.original_balance.checked_sub(&reserve)?;
829 }
830 } else if self.params.strict_extra_currency {
831 reserve.try_add_assign_tokens(ctx.original_balance.tokens)?;
832 } else {
833 reserve.try_add_assign(ctx.original_balance)?;
834 }
835 } else if mode.contains(ReserveCurrencyFlags::REVERSE) {
836 return Err(ActionFailed);
838 }
839
840 if mode.contains(ReserveCurrencyFlags::IGNORE_ERROR) {
841 reserve = reserve.checked_clamp(&ctx.remaining_balance)?;
843 }
844
845 let mut new_balance = CurrencyCollection {
847 tokens: match ctx.remaining_balance.tokens.checked_sub(reserve.tokens) {
848 Some(tokens) => tokens,
849 None => {
850 ctx.action_phase.result_code = ResultCode::NotEnoughBalance as i32;
851 return Err(ActionFailed);
852 }
853 },
854 other: match ctx.remaining_balance.other.checked_sub(&reserve.other) {
855 Ok(other) => other,
856 Err(_) => {
857 ctx.action_phase.result_code = ResultCode::NotEnoughExtraBalance as i32;
858 return Err(ActionFailed);
859 }
860 },
861 };
862
863 reserve.other.normalize()?;
865
866 if mode.contains(ReserveCurrencyFlags::ALL_BUT) {
868 if self.params.strict_extra_currency {
869 std::mem::swap(&mut new_balance.tokens, &mut reserve.tokens);
870 } else {
871 std::mem::swap(&mut new_balance, &mut reserve);
872 }
873 }
874
875 ctx.remaining_balance = new_balance;
877 ctx.reserved_balance.try_add_assign(&reserve)?;
878 ctx.action_phase.special_actions += 1;
879
880 Ok(())
882 }
883
884 fn do_change_library(
886 &self,
887 mode: ChangeLibraryMode,
888 mut lib: LibRef,
889 ctx: &mut ActionContext<'_>,
890 ) -> Result<(), ActionFailed> {
891 const INVALID_MODE: ChangeLibraryMode = ChangeLibraryMode::from_bits_retain(
893 ChangeLibraryMode::ADD_PRIVATE.bits() | ChangeLibraryMode::ADD_PUBLIC.bits(),
894 );
895
896 if mode.contains(ChangeLibraryMode::BOUNCE_ON_ERROR) {
898 ctx.need_bounce_on_fail = true;
899 }
900
901 if mode.contains(INVALID_MODE) {
902 return Err(ActionFailed);
903 }
904
905 let hash = match &lib {
906 LibRef::Cell(cell) => cell.repr_hash(),
907 LibRef::Hash(hash) => hash,
908 };
909
910 let is_masterchain = self.address.is_masterchain();
911
912 let add_public = mode.contains(ChangeLibraryMode::ADD_PUBLIC);
913 if add_public || mode.contains(ChangeLibraryMode::ADD_PRIVATE) {
914 let mut was_public = None;
916 if let Ok(Some(prev)) = ctx.new_state.libraries.get(hash) {
917 if prev.public == add_public {
918 ctx.action_phase.special_actions += 1;
920 return Ok(());
921 } else {
922 was_public = Some(prev.public);
924 lib = LibRef::Cell(prev.root);
925 }
926 }
927
928 let LibRef::Cell(root) = lib else {
929 ctx.action_phase.result_code = ResultCode::NoLibCode as i32;
930 return Err(ActionFailed);
931 };
932
933 let mut stats = ExtStorageStat::with_limits(StorageStatLimits {
934 bit_count: u32::MAX,
935 cell_count: self.config.size_limits.max_library_cells,
936 });
937 if !stats.add_cell(root.as_ref()) {
938 ctx.action_phase.result_code = ResultCode::LibOutOfLimits as i32;
939 return Err(ActionFailed);
940 }
941
942 match ctx.new_state.libraries.set(*root.repr_hash(), SimpleLib {
944 public: add_public,
945 root: root.clone(),
946 }) {
947 Ok(_) if is_masterchain => {
949 if let Some(diff) = &mut ctx.public_libs_diff {
951 match (was_public, add_public) {
952 (None, true) | (Some(false), true) => {
954 insert_temp_change(
955 diff,
956 *root.repr_hash(),
957 PublicLibraryChange::Add(root),
958 );
959 }
960 (Some(true), false) => {
962 insert_temp_change(
963 diff,
964 *root.repr_hash(),
965 PublicLibraryChange::Remove,
966 );
967 }
968 _ => {}
970 }
971 }
972 }
973 Ok(_) => {}
974 Err(_) => {
975 ctx.action_phase.result_code = ResultCode::InvalidLibrariesDict as i32;
976 return Err(ActionFailed);
977 }
978 }
979 } else {
980 match ctx.new_state.libraries.remove(hash) {
982 Ok(Some(lib)) if is_masterchain && lib.public => {
984 if let Some(diff) = &mut ctx.public_libs_diff {
985 insert_temp_change(diff, *hash, PublicLibraryChange::Remove);
986 }
987 }
988 Ok(_) => {}
989 Err(_) => {
990 ctx.action_phase.result_code = ResultCode::InvalidLibrariesDict as i32;
991 return Err(ActionFailed);
992 }
993 }
994 }
995
996 ctx.action_phase.special_actions += 1;
998
999 Ok(())
1001 }
1002}
1003
1004fn insert_temp_change(
1005 diff: &mut ahash::HashMap<HashBytes, LocalLibraryChange>,
1006 key: HashBytes,
1007 change: PublicLibraryChange,
1008) {
1009 match diff.entry(key) {
1010 hash_map::Entry::Vacant(entry) => {
1011 entry.insert(LocalLibraryChange {
1012 is_new: matches!(&change, PublicLibraryChange::Add(_)),
1014 change,
1015 });
1016 }
1017 hash_map::Entry::Occupied(mut entry) => {
1018 let value = entry.get_mut();
1019 match (&value.change, &change) {
1020 (PublicLibraryChange::Add(_), PublicLibraryChange::Remove) if value.is_new => {
1022 entry.remove();
1023 }
1024 (PublicLibraryChange::Remove, PublicLibraryChange::Add(_)) if !value.is_new => {
1026 entry.remove();
1027 }
1028 _ => {
1029 value.change = change;
1030 }
1031 }
1032 }
1033 }
1034}
1035
1036struct LocalLibraryChange {
1037 is_new: bool,
1038 change: PublicLibraryChange,
1039}
1040
1041struct ActionContext<'a> {
1042 need_bounce_on_fail: bool,
1043 strict_extra_currency: bool,
1044 received_message: Option<&'a mut ReceivedMessage>,
1045 original_balance: &'a CurrencyCollection,
1046 remaining_balance: CurrencyCollection,
1047 reserved_balance: CurrencyCollection,
1048 action_fine: &'a mut Tokens,
1049 new_state: &'a mut StateInit,
1050 end_lt: u64,
1051 out_msgs: Vec<Lazy<OwnedMessage>>,
1052 delete_account: bool,
1053 public_libs_diff: Option<ahash::HashMap<HashBytes, LocalLibraryChange>>,
1054
1055 compute_phase: &'a ExecutedComputePhase,
1056 action_phase: &'a mut ActionPhase,
1057}
1058
1059impl ActionContext<'_> {
1060 fn apply_fine_on_error(
1061 &mut self,
1062 balance: &mut CurrencyCollection,
1063 total_fees: &mut Tokens,
1064 charge_action_fees: bool,
1065 ) -> Result<(), Error> {
1066 if charge_action_fees {
1068 self.action_fine
1069 .try_add_assign(self.action_phase.total_action_fees.unwrap_or_default())?;
1070 }
1071
1072 self.action_phase.total_fwd_fees = None;
1076
1077 self.action_phase.total_action_fees = Some(*self.action_fine).filter(|t| !t.is_zero());
1079
1080 balance.tokens.try_sub_assign(*self.action_fine)?;
1081 total_fees.try_add_assign(*self.action_fine)
1082 }
1083
1084 fn rewrite_message_value(
1085 &self,
1086 value: &mut CurrencyCollection,
1087 mut mode: SendMsgFlags,
1088 fees_total: Tokens,
1089 ) -> Result<Tokens, ActionFailed> {
1090 if mode.contains(SendMsgFlags::ALL_BALANCE) {
1092 if self.strict_extra_currency {
1094 value.tokens = self.remaining_balance.tokens;
1096 } else {
1097 *value = self.remaining_balance.clone();
1099 };
1100 mode.remove(SendMsgFlags::PAY_FEE_SEPARATELY);
1102 } else if mode.contains(SendMsgFlags::WITH_REMAINING_BALANCE) {
1103 if let Some(msg) = &self.received_message {
1104 if self.strict_extra_currency {
1107 value.try_add_assign_tokens(msg.balance_remaining.tokens)?;
1109 } else {
1110 value.try_add_assign(&msg.balance_remaining)?;
1112 }
1113 }
1114
1115 if !mode.contains(SendMsgFlags::PAY_FEE_SEPARATELY) {
1116 value.try_sub_assign_tokens(*self.action_fine)?;
1118 value.try_sub_assign_tokens(self.compute_phase.gas_fees)?;
1119 }
1120 }
1121
1122 let mut total = value.tokens;
1124 if mode.contains(SendMsgFlags::PAY_FEE_SEPARATELY) {
1125 total.try_add_assign(fees_total)?;
1126 } else {
1127 value.tokens.try_sub_assign(fees_total)?;
1128 }
1129
1130 Ok(total)
1132 }
1133}
1134
1135struct ActionFailed;
1136
1137impl From<anyhow::Error> for ActionFailed {
1138 #[inline]
1139 fn from(_: anyhow::Error) -> Self {
1140 Self
1141 }
1142}
1143
1144impl From<Error> for ActionFailed {
1145 #[inline]
1146 fn from(_: Error) -> Self {
1147 Self
1148 }
1149}
1150
1151#[derive(Debug, Clone, Copy)]
1152enum SendMsgResult {
1153 Sent,
1154 Rewrite(MessageRewrite),
1155}
1156
1157#[derive(Debug, Clone, Copy)]
1158enum MessageRewrite {
1159 StateInitToCell,
1160 BodyToCell,
1161}
1162
1163impl MessageRewrite {
1164 pub fn next(rewrite: Option<Self>) -> Option<Self> {
1165 match rewrite {
1166 None => Some(Self::StateInitToCell),
1167 Some(Self::StateInitToCell) => Some(Self::BodyToCell),
1168 Some(Self::BodyToCell) => None,
1169 }
1170 }
1171}
1172
1173fn load_state_init_as_slice<'a>(
1174 cs: &mut CellSlice<'a>,
1175 check_libs: bool,
1176) -> Result<CellSlice<'a>, LoadStateInitError> {
1177 let mut res_cs = *cs;
1178
1179 if cs.load_bit()? {
1181 let libs = if cs.load_bit()? {
1182 let state_root = cs.load_reference()?;
1184 if state_root.is_exotic() {
1185 return Err(LoadStateInitError::BadStateInit);
1187 }
1188
1189 let mut cs = state_root.as_slice_allow_exotic();
1191 let Ok(state) = StateInit::load_from(&mut cs) else {
1192 return Err(LoadStateInitError::BadStateInit);
1193 };
1194
1195 if !cs.is_empty() {
1197 return Err(LoadStateInitError::BadStateInit);
1198 }
1199
1200 state.libraries
1201 } else {
1202 match StateInit::load_from(cs) {
1206 Ok(state) => state.libraries,
1207 Err(_) => return Err(LoadStateInitError::BadStateInit),
1208 }
1209 };
1210
1211 if check_libs {
1212 for item in libs.iter() {
1213 let Ok((key, value)) = item else {
1214 return Err(LoadStateInitError::BadStateInit);
1215 };
1216 if &key != value.root.repr_hash() {
1217 return Err(LoadStateInitError::BadStateInit);
1218 }
1219 }
1220 }
1221 }
1222
1223 res_cs.skip_last(cs.size_bits(), cs.size_refs())?;
1224 Ok(res_cs)
1225}
1226
1227enum LoadStateInitError {
1228 BadStateInit,
1229 Other,
1230}
1231
1232impl From<Error> for LoadStateInitError {
1233 #[inline]
1234 fn from(_: Error) -> Self {
1235 Self::Other
1236 }
1237}
1238
1239fn load_body_as_slice<'a>(cs: &mut CellSlice<'a>) -> Result<CellSlice<'a>, Error> {
1240 let res_cs = *cs;
1241
1242 if cs.load_bit()? {
1243 cs.skip_first(0, 1)?;
1245 } else {
1246 cs.load_remaining();
1248 }
1249
1250 Ok(res_cs)
1251}
1252
1253fn rewrite_state_init_to_cell(mut cs: CellSlice<'_>) -> CellBuilder {
1254 let prefix = cs.load_small_uint(2).unwrap();
1256 debug_assert_eq!(prefix, 0b10);
1257
1258 let cell = CellBuilder::build_from(cs).unwrap();
1260
1261 let mut b = CellBuilder::new();
1263 b.store_small_uint(0b11, 2).unwrap();
1264 b.store_reference(cell).unwrap();
1265
1266 b
1268}
1269
1270fn rewrite_body_to_cell(mut cs: CellSlice<'_>) -> CellBuilder {
1271 let prefix = cs.load_bit().unwrap();
1273 debug_assert!(!prefix);
1274
1275 let cell = CellBuilder::build_from(cs).unwrap();
1277
1278 let mut b = CellBuilder::new();
1280 b.store_bit_one().unwrap();
1281 b.store_reference(cell).unwrap();
1282
1283 b
1285}
1286
1287fn build_message(
1288 info: &RelaxedMsgInfo,
1289 state_init_cs: &CellSlice<'_>,
1290 body_cs: &CellSlice<'_>,
1291) -> Result<Lazy<OwnedMessage>, Error> {
1292 CellBuilder::build_from((info, state_init_cs, body_cs)).map(|cell| {
1293 unsafe { Lazy::from_raw_unchecked(cell) }
1295 })
1296}
1297
1298fn update_total_msg_stat(
1299 total_message_size: &mut StorageUsedShort,
1300 stats: CellTreeStats,
1301 root_bits: u16,
1302) {
1303 let bits_diff = VarUint56::new(stats.bit_count.saturating_add(root_bits as _));
1304 let cells_diff = VarUint56::new(stats.cell_count.saturating_add(1));
1305
1306 total_message_size.bits = total_message_size.bits.saturating_add(bits_diff);
1307 total_message_size.cells = total_message_size.cells.saturating_add(cells_diff);
1308}
1309
1310fn check_extra_balance(value: &ExtraCurrencyCollection, limit: usize) -> bool {
1311 for (i, entry) in value.as_dict().iter().enumerate() {
1312 if i > limit || entry.is_err() {
1313 return false;
1314 }
1315 }
1316 true
1317}
1318
1319fn normalize_extra_balance(
1320 value: ExtraCurrencyCollection,
1321 limit: usize,
1322) -> Result<ExtraCurrencyCollection, BalanceExtraError> {
1323 let mut result = value.clone();
1324 for (i, entry) in value.as_dict().iter().enumerate() {
1325 if i > limit {
1326 return Err(BalanceExtraError::OutOfLimit);
1327 }
1328 let (currency_id, other) = entry.map_err(BalanceExtraError::InvalidDict)?;
1329 if other.is_zero() {
1330 result
1331 .as_dict_mut()
1332 .remove(currency_id)
1333 .map_err(BalanceExtraError::InvalidDict)?;
1334 }
1335 }
1336 Ok(result)
1337}
1338
1339enum BalanceExtraError {
1340 OutOfLimit,
1341 InvalidDict(#[allow(unused)] Error),
1342}
1343
1344#[repr(i32)]
1345#[derive(Debug, thiserror::Error)]
1346enum ResultCode {
1347 #[error("invalid action list")]
1348 ActionListInvalid = 32,
1349 #[error("too many actions")]
1350 TooManyActions = 33,
1351 #[error("invalid or unsupported action")]
1352 ActionInvalid = 34,
1353 #[error("invalid source address")]
1354 InvalidSrcAddr = 35,
1355 #[error("invalid destination address")]
1356 InvalidDstAddr = 36,
1357 #[error("not enough balance (base currency)")]
1358 NotEnoughBalance = 37,
1359 #[error("not enough balance (extra currency)")]
1360 NotEnoughExtraBalance = 38,
1361 #[error("failed to fit message into cell")]
1362 FailedToFitMessage = 39,
1363 #[error("message exceeds limits")]
1364 MessageOutOfLimits = 40,
1365 #[error("library code not found")]
1366 NoLibCode = 41,
1367 #[error("failed to change libraries dict")]
1368 InvalidLibrariesDict = 42,
1369 #[error("too many library cells")]
1370 LibOutOfLimits = 43,
1371 #[error("too many extra currencies")]
1372 TooManyExtraCurrencies = 44,
1373 #[error("state exceeds limits")]
1374 StateOutOfLimits = 50,
1375}
1376
1377const MAX_MSG_EXTRA_CURRENCIES: usize = 2;
1379
1380#[cfg(test)]
1381mod tests {
1382 use std::collections::BTreeMap;
1383
1384 use tycho_asm_macros::tvmasm;
1385 use tycho_types::merkle::MerkleProof;
1386 use tycho_types::models::{
1387 Anycast, AuthorityMarksConfig, IntAddr, MessageExtraFlags, MessageLayout, MsgInfo,
1388 RelaxedIntMsgInfo, RelaxedMessage, StdAddr, VarAddr,
1389 };
1390 use tycho_types::num::{Uint9, VarUint248};
1391
1392 use super::*;
1393 use crate::ExecutorParams;
1394 use crate::tests::{make_custom_config, make_default_config, make_default_params};
1395
1396 const STUB_ADDR: StdAddr = StdAddr::new(0, HashBytes::ZERO);
1397 const OK_BALANCE: Tokens = Tokens::new(1_000_000_000);
1398 const OK_GAS: Tokens = Tokens::new(1_000_000);
1399
1400 fn stub_compute_phase(gas_fees: Tokens) -> ExecutedComputePhase {
1401 ExecutedComputePhase {
1402 success: true,
1403 msg_state_used: false,
1404 account_activated: false,
1405 gas_fees,
1406 gas_used: Default::default(),
1407 gas_limit: Default::default(),
1408 gas_credit: None,
1409 mode: 0,
1410 exit_code: 0,
1411 exit_arg: None,
1412 vm_steps: 0,
1413 vm_init_state_hash: Default::default(),
1414 vm_final_state_hash: Default::default(),
1415 }
1416 }
1417
1418 fn empty_action_phase() -> ActionPhase {
1419 ActionPhase {
1420 success: true,
1421 valid: true,
1422 no_funds: false,
1423 status_change: AccountStatusChange::Unchanged,
1424 total_fwd_fees: None,
1425 total_action_fees: None,
1426 result_code: 0,
1427 result_arg: None,
1428 total_actions: 0,
1429 special_actions: 0,
1430 skipped_actions: 0,
1431 messages_created: 0,
1432 action_list_hash: *Cell::empty_cell_ref().repr_hash(),
1433 total_message_size: Default::default(),
1434 }
1435 }
1436
1437 fn make_action_list<I: IntoIterator<Item: Store>>(actions: I) -> Cell {
1438 let mut root = Cell::default();
1439 for action in actions {
1440 root = CellBuilder::build_from((root, action)).unwrap();
1441 }
1442 root
1443 }
1444
1445 fn make_relaxed_message(
1446 info: impl Into<RelaxedMsgInfo>,
1447 init: Option<StateInit>,
1448 body: Option<CellBuilder>,
1449 ) -> Lazy<OwnedRelaxedMessage> {
1450 let body = match &body {
1451 None => Cell::empty_cell_ref().as_slice_allow_exotic(),
1452 Some(cell) => cell.as_full_slice(),
1453 };
1454 Lazy::new(&RelaxedMessage {
1455 info: info.into(),
1456 init,
1457 body,
1458 layout: None,
1459 })
1460 .unwrap()
1461 .cast_into()
1462 }
1463
1464 fn compute_full_stats(msg: &Lazy<OwnedMessage>, params: &ExecutorParams) -> StorageUsedShort {
1465 let msg = 'cell: {
1466 if params.strict_extra_currency {
1467 let mut parsed = msg.load().unwrap();
1468 if let MsgInfo::Int(int) = &mut parsed.info
1469 && !int.value.other.is_empty()
1470 {
1471 int.value.other = ExtraCurrencyCollection::new();
1472 break 'cell CellBuilder::build_from(parsed).unwrap();
1473 }
1474 }
1475 msg.inner().clone()
1476 };
1477
1478 let stats = {
1479 let mut stats = ExtStorageStat::with_limits(StorageStatLimits::UNLIMITED);
1480 assert!(stats.add_cell(msg.as_ref()));
1481 stats.stats()
1482 };
1483 StorageUsedShort {
1484 cells: VarUint56::new(stats.cell_count),
1485 bits: VarUint56::new(stats.bit_count),
1486 }
1487 }
1488
1489 fn original_balance(
1490 state: &ExecutorState<'_>,
1491 compute_phase: &ExecutedComputePhase,
1492 ) -> CurrencyCollection {
1493 state
1494 .balance
1495 .clone()
1496 .checked_add(&compute_phase.gas_fees.into())
1497 .unwrap()
1498 }
1499
1500 #[test]
1501 fn empty_actions() -> Result<()> {
1502 let params = make_default_params();
1503 let config = make_default_config();
1504 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
1505
1506 let compute_phase = stub_compute_phase(OK_GAS);
1507 let prev_total_fees = state.total_fees;
1508 let prev_balance = state.balance.clone();
1509 let prev_end_lt = state.end_lt;
1510
1511 let ActionPhaseFull {
1512 action_phase,
1513 action_fine,
1514 state_exceeds_limits,
1515 bounce,
1516 } = state.action_phase(ActionPhaseContext {
1517 received_message: None,
1518 original_balance: original_balance(&state, &compute_phase),
1519 new_state: StateInit::default(),
1520 actions: Cell::empty_cell(),
1521 compute_phase: &compute_phase,
1522 inspector: None,
1523 })?;
1524
1525 assert_eq!(action_phase, empty_action_phase());
1526 assert_eq!(action_fine, Tokens::ZERO);
1527 assert!(!state_exceeds_limits);
1528 assert!(!bounce);
1529 assert_eq!(state.total_fees, prev_total_fees);
1530 assert_eq!(state.balance, prev_balance);
1531 assert_eq!(state.end_lt, prev_end_lt);
1532 Ok(())
1533 }
1534
1535 #[test]
1536 fn too_many_actions() -> Result<()> {
1537 let params = make_default_params();
1538 let config = make_default_config();
1539 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
1540
1541 let compute_phase = stub_compute_phase(OK_GAS);
1542 let prev_total_fees = state.total_fees;
1543 let prev_balance = state.balance.clone();
1544 let prev_end_lt = state.end_lt;
1545
1546 let actions = make_action_list(
1547 std::iter::repeat_with(|| OutAction::SetCode {
1548 new_code: Cell::empty_cell(),
1549 })
1550 .take(300),
1551 );
1552
1553 let ActionPhaseFull {
1554 action_phase,
1555 action_fine,
1556 state_exceeds_limits,
1557 bounce,
1558 } = state.action_phase(ActionPhaseContext {
1559 received_message: None,
1560 original_balance: original_balance(&state, &compute_phase),
1561 new_state: StateInit::default(),
1562 actions: actions.clone(),
1563 compute_phase: &compute_phase,
1564 inspector: None,
1565 })?;
1566
1567 assert_eq!(action_phase, ActionPhase {
1568 success: false,
1569 valid: false,
1570 result_code: ResultCode::TooManyActions as i32,
1571 result_arg: Some(256),
1572 action_list_hash: *actions.repr_hash(),
1573 ..empty_action_phase()
1574 });
1575 assert_eq!(action_fine, Tokens::ZERO);
1576 assert!(!state_exceeds_limits);
1577 assert!(!bounce);
1578 assert_eq!(state.total_fees, prev_total_fees);
1579 assert_eq!(state.balance, prev_balance);
1580 assert_eq!(state.end_lt, prev_end_lt);
1581 Ok(())
1582 }
1583
1584 #[test]
1585 fn invalid_action_list() -> Result<()> {
1586 let params = make_default_params();
1587 let config = make_default_config();
1588 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
1589
1590 let compute_phase = stub_compute_phase(OK_GAS);
1591 let prev_total_fees = state.total_fees;
1592 let prev_balance = state.balance.clone();
1593 let prev_end_lt = state.end_lt;
1594
1595 let actions = CellBuilder::build_from((
1596 CellBuilder::build_from(MerkleProof::default())?,
1597 OutAction::SetCode {
1598 new_code: Cell::default(),
1599 },
1600 ))?;
1601
1602 let ActionPhaseFull {
1603 action_phase,
1604 action_fine,
1605 state_exceeds_limits,
1606 bounce,
1607 } = state.action_phase(ActionPhaseContext {
1608 received_message: None,
1609 original_balance: original_balance(&state, &compute_phase),
1610 new_state: StateInit::default(),
1611 actions: actions.clone(),
1612 compute_phase: &compute_phase,
1613 inspector: None,
1614 })?;
1615
1616 assert_eq!(action_phase, ActionPhase {
1617 success: false,
1618 valid: false,
1619 result_code: ResultCode::ActionListInvalid as i32,
1620 result_arg: Some(1),
1621 action_list_hash: *actions.repr_hash(),
1622 ..empty_action_phase()
1623 });
1624 assert_eq!(action_fine, Tokens::ZERO);
1625 assert!(!state_exceeds_limits);
1626 assert!(!bounce);
1627 assert_eq!(state.total_fees, prev_total_fees);
1628 assert_eq!(state.balance, prev_balance);
1629 assert_eq!(state.end_lt, prev_end_lt);
1630 Ok(())
1631 }
1632
1633 #[test]
1634 fn invalid_action() -> Result<()> {
1635 let params = make_default_params();
1636 let config = make_default_config();
1637 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
1638
1639 let compute_phase = stub_compute_phase(OK_GAS);
1640 let prev_total_fees = state.total_fees;
1641 let prev_balance = state.balance.clone();
1642 let prev_end_lt = state.end_lt;
1643
1644 let set_code_action = {
1645 let mut b = CellBuilder::new();
1646 OutAction::SetCode {
1647 new_code: Cell::empty_cell(),
1648 }
1649 .store_into(&mut b, Cell::empty_context())?;
1650 b
1651 };
1652 let invalid_action = {
1653 let mut b = CellBuilder::new();
1654 b.store_u32(0xdeafbeaf)?;
1655 b
1656 };
1657
1658 let actions = make_action_list([
1659 set_code_action.as_full_slice(),
1660 set_code_action.as_full_slice(),
1661 invalid_action.as_full_slice(),
1662 set_code_action.as_full_slice(),
1663 set_code_action.as_full_slice(),
1664 ]);
1665
1666 let ActionPhaseFull {
1667 action_phase,
1668 action_fine,
1669 state_exceeds_limits,
1670 bounce,
1671 } = state.action_phase(ActionPhaseContext {
1672 received_message: None,
1673 original_balance: original_balance(&state, &compute_phase),
1674 new_state: StateInit::default(),
1675 actions: actions.clone(),
1676 compute_phase: &compute_phase,
1677 inspector: None,
1678 })?;
1679
1680 assert_eq!(action_phase, ActionPhase {
1681 success: false,
1682 valid: false,
1683 result_code: ResultCode::ActionInvalid as i32,
1684 result_arg: Some(2),
1685 action_list_hash: *actions.repr_hash(),
1686 total_actions: 5,
1687 ..empty_action_phase()
1688 });
1689 assert_eq!(action_fine, Tokens::ZERO);
1690 assert!(!state_exceeds_limits);
1691 assert!(!bounce);
1692 assert_eq!(state.total_fees, prev_total_fees);
1693 assert_eq!(state.balance, prev_balance);
1694 assert_eq!(state.end_lt, prev_end_lt);
1695 Ok(())
1696 }
1697
1698 #[test]
1699 fn strict_reserve_extra_currency() -> Result<()> {
1700 let mut params = make_default_params();
1701 params.strict_extra_currency = true;
1702 let config = make_default_config();
1703 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
1704 let prev_balance = state.balance.clone();
1705
1706 let compute_phase = stub_compute_phase(OK_GAS);
1707 let prev_total_fees = state.total_fees;
1708 let prev_end_lt = state.end_lt;
1709
1710 let actions = make_action_list([OutAction::ReserveCurrency {
1711 mode: ReserveCurrencyFlags::empty(),
1712 value: CurrencyCollection {
1713 tokens: Tokens::ZERO,
1714 other: BTreeMap::from_iter([(123u32, VarUint248::new(10))]).try_into()?,
1715 },
1716 }]);
1717
1718 let ActionPhaseFull {
1719 action_phase,
1720 action_fine,
1721 state_exceeds_limits,
1722 bounce,
1723 } = state.action_phase(ActionPhaseContext {
1724 received_message: None,
1725 original_balance: original_balance(&state, &compute_phase),
1726 new_state: StateInit::default(),
1727 actions: actions.clone(),
1728 compute_phase: &compute_phase,
1729 inspector: None,
1730 })?;
1731
1732 assert_eq!(action_phase, ActionPhase {
1733 success: false,
1734 valid: true,
1735 result_code: ResultCode::ActionInvalid as i32,
1736 result_arg: Some(0),
1737 action_list_hash: *actions.repr_hash(),
1738 total_actions: 1,
1739 ..empty_action_phase()
1740 });
1741 assert_eq!(action_fine, Tokens::ZERO);
1742 assert!(!state_exceeds_limits);
1743 assert!(!bounce);
1744 assert_eq!(state.total_fees, prev_total_fees);
1745 assert_eq!(state.balance, prev_balance);
1746 assert_eq!(state.end_lt, prev_end_lt);
1747 Ok(())
1748 }
1749
1750 #[test]
1751 fn send_single_message() -> Result<()> {
1752 let params = make_default_params();
1753 let config = make_default_config();
1754 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
1755
1756 let compute_phase = stub_compute_phase(OK_GAS);
1757 let prev_total_fees = state.total_fees;
1758 let prev_balance = state.balance.clone();
1759 let prev_end_lt = state.end_lt;
1760
1761 let msg_value = Tokens::new(500_000_000);
1762
1763 let actions = make_action_list([OutAction::SendMsg {
1764 mode: SendMsgFlags::empty(),
1765 out_msg: make_relaxed_message(
1766 RelaxedIntMsgInfo {
1767 dst: STUB_ADDR.into(),
1768 value: msg_value.into(),
1769 ..Default::default()
1770 },
1771 None,
1772 None,
1773 ),
1774 }]);
1775
1776 let ActionPhaseFull {
1777 action_phase,
1778 action_fine,
1779 state_exceeds_limits,
1780 bounce,
1781 } = state.action_phase(ActionPhaseContext {
1782 received_message: None,
1783 original_balance: original_balance(&state, &compute_phase),
1784 new_state: StateInit::default(),
1785 actions: actions.clone(),
1786 compute_phase: &compute_phase,
1787 inspector: None,
1788 })?;
1789
1790 assert_eq!(action_fine, Tokens::ZERO);
1791 assert!(!state_exceeds_limits);
1792 assert!(!bounce);
1793
1794 assert_eq!(state.out_msgs.len(), 1);
1795 assert_eq!(state.end_lt, prev_end_lt + 1);
1796 let last_msg = state.out_msgs.last().unwrap();
1797
1798 let msg_info = {
1799 let msg = last_msg.load()?;
1800 assert!(msg.init.is_none());
1801 assert_eq!(msg.body, Default::default());
1802 match msg.info {
1803 MsgInfo::Int(info) => info,
1804 e => panic!("unexpected msg info {e:?}"),
1805 }
1806 };
1807 assert_eq!(msg_info.src, STUB_ADDR.into());
1808 assert_eq!(msg_info.dst, STUB_ADDR.into());
1809 assert!(msg_info.ihr_disabled);
1810 assert!(!msg_info.bounce);
1811 assert!(!msg_info.bounced);
1812 assert_eq!(msg_info.created_at, params.block_unixtime);
1813 assert_eq!(msg_info.created_lt, prev_end_lt);
1814
1815 let expected_fwd_fees = Tokens::new(config.fwd_prices.lump_price as _);
1816 let expected_first_frac = config.fwd_prices.get_first_part(expected_fwd_fees);
1817
1818 assert_eq!(msg_info.value, (msg_value - expected_fwd_fees).into());
1819 assert_eq!(msg_info.fwd_fee, expected_fwd_fees - expected_first_frac);
1820 assert_eq!(msg_info.extra_flags, MessageExtraFlags::default());
1821
1822 assert_eq!(action_phase, ActionPhase {
1823 total_fwd_fees: Some(expected_fwd_fees),
1824 total_action_fees: Some(expected_first_frac),
1825 total_actions: 1,
1826 messages_created: 1,
1827 action_list_hash: *actions.repr_hash(),
1828 total_message_size: compute_full_stats(last_msg, ¶ms),
1829 ..empty_action_phase()
1830 });
1831
1832 assert_eq!(state.total_fees, prev_total_fees + expected_first_frac);
1833 assert_eq!(state.balance.other, prev_balance.other);
1834 assert_eq!(state.balance.tokens, prev_balance.tokens - msg_value);
1835
1836 Ok(())
1837 }
1838
1839 #[test]
1840 fn send_all_balance() -> Result<()> {
1841 let params = make_default_params();
1842 let config = make_default_config();
1843 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
1844
1845 let compute_phase = stub_compute_phase(OK_GAS);
1846 let prev_total_fees = state.total_fees;
1847 let prev_balance = state.balance.clone();
1848 let prev_end_lt = state.end_lt;
1849
1850 let actions = make_action_list([OutAction::SendMsg {
1851 mode: SendMsgFlags::ALL_BALANCE,
1852 out_msg: make_relaxed_message(
1853 RelaxedIntMsgInfo {
1854 dst: STUB_ADDR.into(),
1855 value: CurrencyCollection::ZERO,
1856 ..Default::default()
1857 },
1858 None,
1859 None,
1860 ),
1861 }]);
1862
1863 let ActionPhaseFull {
1864 action_phase,
1865 action_fine,
1866 state_exceeds_limits,
1867 bounce,
1868 } = state.action_phase(ActionPhaseContext {
1869 received_message: None,
1870 original_balance: original_balance(&state, &compute_phase),
1871 new_state: StateInit::default(),
1872 actions: actions.clone(),
1873 compute_phase: &compute_phase,
1874 inspector: None,
1875 })?;
1876
1877 assert_eq!(action_fine, Tokens::ZERO);
1878 assert!(!state_exceeds_limits);
1879 assert!(!bounce);
1880
1881 assert_eq!(state.out_msgs.len(), 1);
1882 assert_eq!(state.end_lt, prev_end_lt + 1);
1883 let last_msg = state.out_msgs.last().unwrap();
1884
1885 let msg_info = {
1886 let msg = last_msg.load()?;
1887 assert!(msg.init.is_none());
1888 assert_eq!(msg.body, Default::default());
1889 match msg.info {
1890 MsgInfo::Int(info) => info,
1891 e => panic!("unexpected msg info {e:?}"),
1892 }
1893 };
1894 assert_eq!(msg_info.src, STUB_ADDR.into());
1895 assert_eq!(msg_info.dst, STUB_ADDR.into());
1896 assert!(msg_info.ihr_disabled);
1897 assert!(!msg_info.bounce);
1898 assert!(!msg_info.bounced);
1899 assert_eq!(msg_info.created_at, params.block_unixtime);
1900 assert_eq!(msg_info.created_lt, prev_end_lt);
1901
1902 let expected_fwd_fees = Tokens::new(config.fwd_prices.lump_price as _);
1903 let expected_first_frac = config.fwd_prices.get_first_part(expected_fwd_fees);
1904
1905 assert_eq!(
1906 msg_info.value,
1907 (prev_balance.tokens - expected_fwd_fees).into()
1908 );
1909 assert_eq!(msg_info.fwd_fee, expected_fwd_fees - expected_first_frac);
1910 assert_eq!(msg_info.extra_flags, MessageExtraFlags::default());
1911
1912 assert_eq!(action_phase, ActionPhase {
1913 total_fwd_fees: Some(expected_fwd_fees),
1914 total_action_fees: Some(expected_first_frac),
1915 total_actions: 1,
1916 messages_created: 1,
1917 action_list_hash: *actions.repr_hash(),
1918 total_message_size: compute_full_stats(last_msg, ¶ms),
1919 ..empty_action_phase()
1920 });
1921
1922 assert_eq!(state.total_fees, prev_total_fees + expected_first_frac);
1923 assert_eq!(state.balance, CurrencyCollection::ZERO);
1924
1925 Ok(())
1926 }
1927
1928 #[test]
1929 fn strict_send_all_balance() -> Result<()> {
1930 let mut params = make_default_params();
1931 params.strict_extra_currency = true;
1932 let config = make_default_config();
1933
1934 let mut state =
1935 ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, CurrencyCollection {
1936 tokens: OK_BALANCE,
1937 other: BTreeMap::from_iter([(0u32, VarUint248::new(1000))]).try_into()?,
1938 });
1939
1940 let compute_phase = stub_compute_phase(OK_GAS);
1941 let prev_total_fees = state.total_fees;
1942 let prev_balance = state.balance.clone();
1943 let prev_end_lt = state.end_lt;
1944
1945 let sent_value = CurrencyCollection {
1946 tokens: Tokens::ZERO,
1947 other: BTreeMap::from_iter([(0u32, VarUint248::new(1))]).try_into()?,
1948 };
1949
1950 let actions = make_action_list([OutAction::SendMsg {
1951 mode: SendMsgFlags::ALL_BALANCE,
1952 out_msg: make_relaxed_message(
1953 RelaxedIntMsgInfo {
1954 dst: STUB_ADDR.into(),
1955 value: sent_value.clone(),
1956 ..Default::default()
1957 },
1958 None,
1959 None,
1960 ),
1961 }]);
1962
1963 let ActionPhaseFull {
1964 action_phase,
1965 action_fine,
1966 state_exceeds_limits,
1967 bounce,
1968 } = state.action_phase(ActionPhaseContext {
1969 received_message: None,
1970 original_balance: original_balance(&state, &compute_phase),
1971 new_state: StateInit::default(),
1972 actions: actions.clone(),
1973 compute_phase: &compute_phase,
1974 inspector: None,
1975 })?;
1976
1977 assert_eq!(action_fine, Tokens::ZERO);
1978 assert!(!state_exceeds_limits);
1979 assert!(!bounce);
1980
1981 assert_eq!(state.out_msgs.len(), 1);
1982 assert_eq!(state.end_lt, prev_end_lt + 1);
1983 let last_msg = state.out_msgs.last().unwrap();
1984
1985 let msg_info = {
1986 let msg = last_msg.load()?;
1987 assert!(msg.init.is_none());
1988 assert_eq!(msg.body, Default::default());
1989 match msg.info {
1990 MsgInfo::Int(info) => info,
1991 e => panic!("unexpected msg info {e:?}"),
1992 }
1993 };
1994 assert_eq!(msg_info.src, STUB_ADDR.into());
1995 assert_eq!(msg_info.dst, STUB_ADDR.into());
1996 assert!(msg_info.ihr_disabled);
1997 assert!(!msg_info.bounce);
1998 assert!(!msg_info.bounced);
1999 assert_eq!(msg_info.created_at, params.block_unixtime);
2000 assert_eq!(msg_info.created_lt, prev_end_lt);
2001
2002 let expected_fwd_fees = Tokens::new(config.fwd_prices.lump_price as _);
2003 let expected_first_frac = config.fwd_prices.get_first_part(expected_fwd_fees);
2004
2005 assert_eq!(msg_info.value, CurrencyCollection {
2006 tokens: prev_balance.tokens - expected_fwd_fees,
2007 other: sent_value.other.clone(),
2008 });
2009 assert_eq!(msg_info.fwd_fee, expected_fwd_fees - expected_first_frac);
2010 assert_eq!(msg_info.extra_flags, MessageExtraFlags::default());
2011
2012 assert_eq!(action_phase, ActionPhase {
2013 total_fwd_fees: Some(expected_fwd_fees),
2014 total_action_fees: Some(expected_first_frac),
2015 total_actions: 1,
2016 messages_created: 1,
2017 action_list_hash: *actions.repr_hash(),
2018 total_message_size: compute_full_stats(last_msg, ¶ms),
2019 ..empty_action_phase()
2020 });
2021
2022 assert_eq!(state.total_fees, prev_total_fees + expected_first_frac);
2023 assert_eq!(state.balance.tokens, Tokens::ZERO);
2024 assert_eq!(
2025 state.balance.other,
2026 prev_balance.other.checked_sub(&sent_value.other)?
2027 );
2028
2029 Ok(())
2030 }
2031
2032 #[test]
2033 fn strict_send_all_balance_destroy() -> Result<()> {
2034 struct TestCase {
2035 balance: CurrencyCollection,
2036 to_send: CurrencyCollection,
2037 expected_end_status: AccountStatus,
2038 }
2039
2040 let mut params = make_default_params();
2041 params.strict_extra_currency = true;
2042 let config = make_default_config();
2043
2044 for TestCase {
2045 balance,
2046 to_send,
2047 expected_end_status,
2048 } in [
2049 TestCase {
2051 balance: OK_BALANCE.into(),
2052 to_send: CurrencyCollection::ZERO,
2053 expected_end_status: AccountStatus::NotExists,
2054 },
2055 TestCase {
2057 balance: CurrencyCollection {
2058 tokens: OK_BALANCE,
2059 other: BTreeMap::from_iter([(0u32, VarUint248::new(1000))]).try_into()?,
2060 },
2061 to_send: CurrencyCollection::ZERO,
2062 expected_end_status: AccountStatus::Uninit,
2063 },
2064 TestCase {
2066 balance: CurrencyCollection {
2067 tokens: OK_BALANCE,
2068 other: BTreeMap::from_iter([(0u32, VarUint248::new(1000))]).try_into()?,
2069 },
2070 to_send: CurrencyCollection::ZERO,
2071 expected_end_status: AccountStatus::Uninit,
2072 },
2073 TestCase {
2075 balance: CurrencyCollection {
2076 tokens: OK_BALANCE,
2077 other: BTreeMap::from_iter([(0u32, VarUint248::new(1000))]).try_into()?,
2078 },
2079 to_send: CurrencyCollection {
2080 tokens: OK_BALANCE,
2081 other: BTreeMap::from_iter([(0u32, VarUint248::new(1))]).try_into()?,
2082 },
2083 expected_end_status: AccountStatus::Uninit,
2084 },
2085 TestCase {
2087 balance: CurrencyCollection {
2088 tokens: OK_BALANCE,
2089 other: BTreeMap::from_iter([(0u32, VarUint248::new(1000))]).try_into()?,
2090 },
2091 to_send: CurrencyCollection {
2092 tokens: OK_BALANCE,
2093 other: BTreeMap::from_iter([(0u32, VarUint248::new(1000))]).try_into()?,
2094 },
2095 expected_end_status: AccountStatus::NotExists,
2096 },
2097 ] {
2098 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, balance);
2099
2100 let compute_phase = stub_compute_phase(OK_GAS);
2101 let prev_total_fees = state.total_fees;
2102 let prev_balance = state.balance.clone();
2103 let prev_end_lt = state.end_lt;
2104
2105 let actions = make_action_list([OutAction::SendMsg {
2106 mode: SendMsgFlags::ALL_BALANCE | SendMsgFlags::DELETE_IF_EMPTY,
2107 out_msg: make_relaxed_message(
2108 RelaxedIntMsgInfo {
2109 dst: STUB_ADDR.into(),
2110 value: to_send.clone(),
2111 ..Default::default()
2112 },
2113 None,
2114 None,
2115 ),
2116 }]);
2117
2118 let ActionPhaseFull {
2119 action_phase,
2120 action_fine,
2121 state_exceeds_limits,
2122 bounce,
2123 } = state.action_phase(ActionPhaseContext {
2124 received_message: None,
2125 original_balance: original_balance(&state, &compute_phase),
2126 new_state: StateInit::default(),
2127 actions: actions.clone(),
2128 compute_phase: &compute_phase,
2129 inspector: None,
2130 })?;
2131
2132 assert_eq!(action_fine, Tokens::ZERO);
2133 assert!(!state_exceeds_limits);
2134 assert!(!bounce);
2135
2136 assert_eq!(state.end_status, expected_end_status);
2137 assert_eq!(state.out_msgs.len(), 1);
2138 assert_eq!(state.end_lt, prev_end_lt + 1);
2139 let last_msg = state.out_msgs.last().unwrap();
2140
2141 let msg_info = {
2142 let msg = last_msg.load()?;
2143 assert!(msg.init.is_none());
2144 assert_eq!(msg.body, Default::default());
2145 match msg.info {
2146 MsgInfo::Int(info) => info,
2147 e => panic!("unexpected msg info {e:?}"),
2148 }
2149 };
2150 assert_eq!(msg_info.src, STUB_ADDR.into());
2151 assert_eq!(msg_info.dst, STUB_ADDR.into());
2152 assert!(msg_info.ihr_disabled);
2153 assert!(!msg_info.bounce);
2154 assert!(!msg_info.bounced);
2155 assert_eq!(msg_info.created_at, params.block_unixtime);
2156 assert_eq!(msg_info.created_lt, prev_end_lt);
2157
2158 let expected_fwd_fees = Tokens::new(config.fwd_prices.lump_price as _);
2159 let expected_first_frac = config.fwd_prices.get_first_part(expected_fwd_fees);
2160
2161 assert_eq!(msg_info.value, CurrencyCollection {
2162 tokens: prev_balance.tokens - expected_fwd_fees,
2163 other: to_send.other.clone(),
2164 });
2165 assert_eq!(msg_info.fwd_fee, expected_fwd_fees - expected_first_frac);
2166 assert_eq!(msg_info.extra_flags, MessageExtraFlags::default());
2167
2168 assert_eq!(action_phase, ActionPhase {
2169 total_fwd_fees: Some(expected_fwd_fees),
2170 total_action_fees: Some(expected_first_frac),
2171 total_actions: 1,
2172 messages_created: 1,
2173 action_list_hash: *actions.repr_hash(),
2174 total_message_size: compute_full_stats(last_msg, ¶ms),
2175 status_change: AccountStatusChange::Deleted,
2176 ..empty_action_phase()
2177 });
2178
2179 assert_eq!(state.total_fees, prev_total_fees + expected_first_frac);
2180 assert_eq!(state.balance.tokens, Tokens::ZERO);
2181 assert_eq!(
2182 state.balance.other,
2183 prev_balance.other.checked_sub(&to_send.other)?
2184 );
2185 }
2186
2187 Ok(())
2188 }
2189
2190 #[test]
2191 fn send_all_not_reserved() -> Result<()> {
2192 let params = make_default_params();
2193 let config = make_default_config();
2194 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
2195
2196 let compute_phase = stub_compute_phase(OK_GAS);
2197 let prev_total_fees = state.total_fees;
2198 let prev_end_lt = state.end_lt;
2199
2200 let expected_balance = CurrencyCollection::from(state.balance.tokens / 4);
2201 let actions = make_action_list([
2202 OutAction::ReserveCurrency {
2203 mode: ReserveCurrencyFlags::empty(),
2204 value: expected_balance.clone(),
2205 },
2206 OutAction::SendMsg {
2207 mode: SendMsgFlags::ALL_BALANCE,
2208 out_msg: make_relaxed_message(
2209 RelaxedIntMsgInfo {
2210 dst: STUB_ADDR.into(),
2211 value: CurrencyCollection::ZERO,
2212 ..Default::default()
2213 },
2214 None,
2215 None,
2216 ),
2217 },
2218 ]);
2219
2220 let ActionPhaseFull {
2221 action_phase,
2222 action_fine,
2223 state_exceeds_limits,
2224 bounce,
2225 } = state.action_phase(ActionPhaseContext {
2226 received_message: None,
2227 original_balance: original_balance(&state, &compute_phase),
2228 new_state: StateInit::default(),
2229 actions: actions.clone(),
2230 compute_phase: &compute_phase,
2231 inspector: None,
2232 })?;
2233
2234 assert_eq!(state.out_msgs.len(), 1);
2235 assert_eq!(state.end_lt, prev_end_lt + 1);
2236 let last_msg = state.out_msgs.last().unwrap();
2237
2238 let msg_info = {
2239 let msg = last_msg.load()?;
2240 assert!(msg.init.is_none());
2241 assert_eq!(msg.body, Default::default());
2242 match msg.info {
2243 MsgInfo::Int(info) => info,
2244 e => panic!("unexpected msg info {e:?}"),
2245 }
2246 };
2247 assert_eq!(msg_info.src, STUB_ADDR.into());
2248 assert_eq!(msg_info.dst, STUB_ADDR.into());
2249 assert!(msg_info.ihr_disabled);
2250 assert!(!msg_info.bounce);
2251 assert!(!msg_info.bounced);
2252 assert_eq!(msg_info.created_at, params.block_unixtime);
2253 assert_eq!(msg_info.created_lt, prev_end_lt);
2254
2255 let expected_fwd_fees = Tokens::new(config.fwd_prices.lump_price as _);
2256 let expected_first_frac = config.fwd_prices.get_first_part(expected_fwd_fees);
2257
2258 assert_eq!(
2259 msg_info.value,
2260 (OK_BALANCE * 3 / 4 - expected_fwd_fees).into()
2261 );
2262 assert_eq!(msg_info.fwd_fee, expected_fwd_fees - expected_first_frac);
2263 assert_eq!(msg_info.extra_flags, MessageExtraFlags::default());
2264
2265 assert_eq!(action_phase, ActionPhase {
2266 total_fwd_fees: Some(expected_fwd_fees),
2267 total_action_fees: Some(expected_first_frac),
2268 total_actions: 2,
2269 messages_created: 1,
2270 special_actions: 1,
2271 action_list_hash: *actions.repr_hash(),
2272 total_message_size: compute_full_stats(last_msg, ¶ms),
2273 ..empty_action_phase()
2274 });
2275 assert_eq!(action_fine, Tokens::ZERO);
2276 assert!(!state_exceeds_limits);
2277 assert!(!bounce);
2278
2279 assert_eq!(state.total_fees, prev_total_fees + expected_first_frac);
2280 assert_eq!(state.balance, expected_balance);
2281 Ok(())
2282 }
2283
2284 #[test]
2285 fn cant_send_authority_marks_by_all_balance() -> Result<()> {
2286 let params = ExecutorParams {
2287 authority_marks_enabled: true,
2288 strict_extra_currency: false,
2289 ..make_default_params()
2290 };
2291
2292 let config = make_custom_config(|config| {
2293 config.set_authority_marks_config(&AuthorityMarksConfig {
2294 authority_addresses: Dict::new(),
2295 black_mark_id: 100,
2296 white_mark_id: 101,
2297 })?;
2298 Ok(())
2299 });
2300
2301 let mut state = ExecutorState::new_active(
2302 ¶ms,
2303 &config,
2304 &STUB_ADDR,
2305 CurrencyCollection {
2306 tokens: OK_BALANCE,
2307 other: BTreeMap::from_iter([
2308 (100u32, VarUint248::new(500)), (101u32, VarUint248::new(500)),
2310 (200u32, VarUint248::new(500)),
2311 ])
2312 .try_into()?,
2313 },
2314 Cell::default(),
2315 tvmasm!("ACCEPT"),
2316 );
2317
2318 let compute_phase = stub_compute_phase(OK_GAS);
2319
2320 let prev_end_lt = state.end_lt;
2321 let prev_balance = state.balance.clone();
2322
2323 let actions = make_action_list([OutAction::SendMsg {
2324 mode: SendMsgFlags::ALL_BALANCE,
2325 out_msg: make_relaxed_message(
2326 RelaxedIntMsgInfo {
2327 dst: STUB_ADDR.into(),
2328 value: CurrencyCollection {
2329 tokens: Tokens::new(100_000_000),
2330 other: ExtraCurrencyCollection::new(),
2331 },
2332 ..Default::default()
2333 },
2334 None,
2335 None,
2336 ),
2337 }]);
2338
2339 let ActionPhaseFull {
2340 action_phase,
2341 action_fine,
2342 state_exceeds_limits,
2343 bounce,
2344 } = state.action_phase(ActionPhaseContext {
2345 received_message: None,
2346 original_balance: original_balance(&state, &compute_phase),
2347 new_state: StateInit::default(),
2348 actions,
2349 compute_phase: &compute_phase,
2350 inspector: None,
2351 })?;
2352
2353 assert!(!action_phase.success);
2354 assert!(action_phase.valid);
2355 assert_eq!(
2356 action_phase.result_code,
2357 ResultCode::NotEnoughExtraBalance as i32
2358 );
2359 assert_eq!(action_phase.messages_created, 0);
2360 assert_eq!(action_phase.total_actions, 1);
2361 assert_eq!(action_fine, Tokens::ZERO);
2362 assert!(!state_exceeds_limits);
2363 assert!(!bounce);
2364
2365 assert_eq!(state.out_msgs.len(), 0);
2366 assert_eq!(state.end_lt, prev_end_lt);
2367
2368 assert_eq!(state.balance, prev_balance);
2369 Ok(())
2370 }
2371
2372 #[test]
2373 fn authority_addresses_can_send_marks() -> Result<()> {
2374 let params = ExecutorParams {
2375 authority_marks_enabled: true,
2376 ..make_default_params()
2377 };
2378 let config = make_custom_config(|config| {
2379 config.set_authority_marks_config(&AuthorityMarksConfig {
2380 authority_addresses: Dict::try_from_btree(&BTreeMap::from_iter([(
2381 HashBytes::ZERO,
2382 (),
2383 )]))?,
2384 black_mark_id: 100,
2385 white_mark_id: 101,
2386 })?;
2387 Ok(())
2388 });
2389
2390 let mut state = ExecutorState::new_active(
2391 ¶ms,
2392 &config,
2393 &StdAddr::new(-1, HashBytes::ZERO),
2394 CurrencyCollection {
2395 tokens: OK_BALANCE,
2396 other: BTreeMap::from_iter([
2397 (100u32, VarUint248::new(1000)), (101u32, VarUint248::new(100)),
2399 ])
2400 .try_into()?,
2401 },
2402 Cell::default(),
2403 tvmasm!("ACCEPT"),
2404 );
2405
2406 let compute_phase = stub_compute_phase(OK_GAS);
2407 let prev_balance = state.balance.clone();
2408
2409 let actions_marks = make_action_list([OutAction::SendMsg {
2411 mode: SendMsgFlags::PAY_FEE_SEPARATELY,
2412 out_msg: make_relaxed_message(
2413 RelaxedIntMsgInfo {
2414 dst: STUB_ADDR.into(),
2415 value: CurrencyCollection {
2416 tokens: Tokens::ZERO,
2417 other: BTreeMap::from_iter([(100u32, VarUint248::new(10))]).try_into()?,
2418 },
2419 ..Default::default()
2420 },
2421 None,
2422 None,
2423 ),
2424 }]);
2425
2426 let ActionPhaseFull {
2427 action_phase: action_phase_normal,
2428 action_fine: action_fine_normal,
2429 state_exceeds_limits: state_exceeds_limits_normal,
2430 bounce: bounce_normal,
2431 } = state.action_phase(ActionPhaseContext {
2432 received_message: None,
2433 original_balance: original_balance(&state, &compute_phase),
2434 new_state: StateInit::default(),
2435 actions: actions_marks.clone(),
2436 compute_phase: &compute_phase,
2437 inspector: None,
2438 })?;
2439
2440 assert!(action_phase_normal.success);
2441 assert!(action_phase_normal.valid);
2442 assert_eq!(action_phase_normal.result_code, 0);
2443 assert_eq!(action_phase_normal.messages_created, 1);
2444 assert_eq!(action_fine_normal, Tokens::ZERO);
2445
2446 assert_ne!(state.balance.tokens, prev_balance.tokens); assert_eq!(
2448 state.balance.other,
2449 BTreeMap::from_iter([
2450 (100u32, VarUint248::new(990)), (101u32, VarUint248::new(100)),
2452 ])
2453 .try_into()?
2454 );
2455 assert!(!state_exceeds_limits_normal);
2456 assert!(!bounce_normal);
2457
2458 assert_eq!(state.out_msgs.len(), 1);
2459
2460 Ok(())
2461 }
2462
2463 #[test]
2464 fn cant_send_authority_marks_directly() -> Result<()> {
2465 let params = ExecutorParams {
2466 authority_marks_enabled: true,
2467 ..make_default_params()
2468 };
2469
2470 let config = make_custom_config(|config| {
2471 config.set_authority_marks_config(&AuthorityMarksConfig {
2472 authority_addresses: Dict::new(),
2473 black_mark_id: 100,
2474 white_mark_id: 101,
2475 })?;
2476 Ok(())
2477 });
2478
2479 let mut state = ExecutorState::new_active(
2480 ¶ms,
2481 &config,
2482 &STUB_ADDR,
2483 CurrencyCollection {
2484 tokens: OK_BALANCE,
2485 other: BTreeMap::from_iter([
2486 (100u32, VarUint248::new(500)), (101u32, VarUint248::new(500)),
2488 ])
2489 .try_into()?,
2490 },
2491 Cell::default(),
2492 tvmasm!("ACCEPT"),
2493 );
2494
2495 let compute_phase = stub_compute_phase(OK_GAS);
2496 let prev_balance = state.balance.clone();
2497 let prev_total_fees = state.total_fees;
2498 let prev_end_lt = state.end_lt;
2499
2500 let actions_black = make_action_list([OutAction::SendMsg {
2502 mode: SendMsgFlags::PAY_FEE_SEPARATELY,
2503 out_msg: make_relaxed_message(
2504 RelaxedIntMsgInfo {
2505 dst: STUB_ADDR.into(),
2506 value: CurrencyCollection {
2507 tokens: Tokens::ZERO,
2508 other: BTreeMap::from_iter([(100u32, VarUint248::new(10))]).try_into()?,
2509 },
2510 ..Default::default()
2511 },
2512 None,
2513 None,
2514 ),
2515 }]);
2516
2517 let ActionPhaseFull {
2518 action_phase: action_phase_black,
2519 action_fine: action_fine_black,
2520 state_exceeds_limits: state_exceeds_limits_black,
2521 bounce: bounce_black,
2522 } = state.action_phase(ActionPhaseContext {
2523 received_message: None,
2524 original_balance: original_balance(&state, &compute_phase),
2525 new_state: StateInit::default(),
2526 actions: actions_black.clone(),
2527 compute_phase: &compute_phase,
2528 inspector: None,
2529 })?;
2530
2531 assert_eq!(action_phase_black, ActionPhase {
2532 success: false,
2533 valid: true,
2534 no_funds: true,
2535 result_code: ResultCode::NotEnoughExtraBalance as i32,
2536 result_arg: Some(0),
2537 total_actions: 1,
2538 action_list_hash: *actions_black.repr_hash(),
2539 ..empty_action_phase()
2540 });
2541
2542 assert_eq!(action_fine_black, Tokens::ZERO);
2543 assert!(!state_exceeds_limits_black);
2544 assert!(!bounce_black);
2545
2546 assert_eq!(state.total_fees, prev_total_fees);
2547 assert_eq!(state.balance, prev_balance);
2548 assert_eq!(state.end_lt, prev_end_lt);
2549 assert!(state.out_msgs.is_empty());
2550
2551 let actions_normal = make_action_list([OutAction::SendMsg {
2553 mode: SendMsgFlags::PAY_FEE_SEPARATELY,
2554 out_msg: make_relaxed_message(
2555 RelaxedIntMsgInfo {
2556 dst: STUB_ADDR.into(),
2557 value: CurrencyCollection {
2558 tokens: Tokens::ZERO,
2559 other: ExtraCurrencyCollection::new(),
2560 },
2561 ..Default::default()
2562 },
2563 None,
2564 None,
2565 ),
2566 }]);
2567
2568 let ActionPhaseFull {
2569 action_phase: action_phase_normal,
2570 action_fine: action_fine_normal,
2571 state_exceeds_limits: state_exceeds_limits_normal,
2572 bounce: bounce_normal,
2573 } = state.action_phase(ActionPhaseContext {
2574 received_message: None,
2575 original_balance: original_balance(&state, &compute_phase),
2576 new_state: StateInit::default(),
2577 actions: actions_normal.clone(),
2578 compute_phase: &compute_phase,
2579 inspector: None,
2580 })?;
2581
2582 assert!(action_phase_normal.success);
2583 assert!(action_phase_normal.valid);
2584 assert_eq!(action_phase_normal.result_code, 0);
2585 assert_eq!(action_phase_normal.messages_created, 1);
2586 assert_eq!(action_fine_normal, Tokens::ZERO);
2587 assert!(!state_exceeds_limits_normal);
2588 assert!(!bounce_normal);
2589
2590 assert_eq!(state.out_msgs.len(), 1);
2591
2592 Ok(())
2593 }
2594
2595 #[test]
2596 fn set_code() -> Result<()> {
2597 let params = make_default_params();
2598 let config = make_default_config();
2599
2600 let orig_data = CellBuilder::build_from(u32::MIN)?;
2601 let final_data = CellBuilder::build_from(u32::MAX)?;
2602
2603 let temp_code = Boc::decode(tvmasm!("NOP NOP"))?;
2604 let final_code = Boc::decode(tvmasm!("NOP"))?;
2605
2606 let mut state = ExecutorState::new_active(
2607 ¶ms,
2608 &config,
2609 &STUB_ADDR,
2610 OK_BALANCE,
2611 orig_data,
2612 tvmasm!("ACCEPT"),
2613 );
2614
2615 let compute_phase = stub_compute_phase(OK_GAS);
2616 let prev_total_fees = state.total_fees;
2617 let prev_balance = state.balance.clone();
2618
2619 let actions = make_action_list([
2620 OutAction::SetCode {
2621 new_code: temp_code,
2622 },
2623 OutAction::SetCode {
2624 new_code: final_code.clone(),
2625 },
2626 ]);
2627
2628 let AccountState::Active(mut new_state) = state.state.clone() else {
2629 panic!("unexpected account state");
2630 };
2631 new_state.data = Some(final_data.clone());
2632
2633 let ActionPhaseFull {
2634 action_phase,
2635 action_fine,
2636 state_exceeds_limits,
2637 bounce,
2638 } = state.action_phase(ActionPhaseContext {
2639 received_message: None,
2640 original_balance: original_balance(&state, &compute_phase),
2641 new_state,
2642 actions: actions.clone(),
2643 compute_phase: &compute_phase,
2644 inspector: None,
2645 })?;
2646
2647 assert_eq!(action_phase, ActionPhase {
2648 total_actions: 2,
2649 special_actions: 2,
2650 action_list_hash: *actions.repr_hash(),
2651 ..empty_action_phase()
2652 });
2653 assert_eq!(action_fine, Tokens::ZERO);
2654 assert!(!state_exceeds_limits);
2655 assert!(!bounce);
2656 assert_eq!(state.end_status, AccountStatus::Active);
2657 assert_eq!(
2658 state.state,
2659 AccountState::Active(StateInit {
2660 code: Some(final_code),
2661 data: Some(final_data),
2662 ..Default::default()
2663 })
2664 );
2665 assert_eq!(state.total_fees, prev_total_fees);
2666 assert_eq!(state.balance, prev_balance);
2667 Ok(())
2668 }
2669
2670 #[test]
2671 fn invalid_dst_addr() -> Result<()> {
2672 let params = make_default_params();
2673 let config = make_default_config();
2674
2675 let targets = [
2676 IntAddr::Std(StdAddr::new(123, HashBytes::ZERO)),
2678 IntAddr::Std({
2680 let mut addr = STUB_ADDR;
2681 let mut b = CellBuilder::new();
2682 b.store_u16(0xaabb)?;
2683 addr.anycast = Some(Box::new(Anycast::from_slice(&b.as_data_slice())?));
2684 addr
2685 }),
2686 IntAddr::Var(VarAddr {
2688 anycast: None,
2689 address_len: Uint9::new(80),
2690 workchain: 0,
2691 address: vec![0; 10],
2692 }),
2693 ];
2694
2695 for dst in targets {
2696 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
2697
2698 let compute_phase = stub_compute_phase(OK_GAS);
2699 let prev_total_fees = state.total_fees;
2700 let prev_balance = state.balance.clone();
2701 let prev_end_lt = state.end_lt;
2702
2703 let actions = make_action_list([OutAction::SendMsg {
2704 mode: SendMsgFlags::ALL_BALANCE,
2705 out_msg: make_relaxed_message(
2706 RelaxedIntMsgInfo {
2707 dst,
2708 ..Default::default()
2709 },
2710 None,
2711 None,
2712 ),
2713 }]);
2714
2715 let ActionPhaseFull {
2716 action_phase,
2717 action_fine,
2718 state_exceeds_limits,
2719 bounce,
2720 } = state.action_phase(ActionPhaseContext {
2721 received_message: None,
2722 original_balance: original_balance(&state, &compute_phase),
2723 new_state: StateInit::default(),
2724 actions: actions.clone(),
2725 compute_phase: &compute_phase,
2726 inspector: None,
2727 })?;
2728
2729 assert_eq!(action_phase, ActionPhase {
2730 success: false,
2731 total_actions: 1,
2732 messages_created: 0,
2733 result_code: ResultCode::InvalidDstAddr as _,
2734 result_arg: Some(0),
2735 action_list_hash: *actions.repr_hash(),
2736 ..empty_action_phase()
2737 });
2738 assert_eq!(action_fine, Tokens::ZERO);
2739 assert!(!state_exceeds_limits);
2740 assert!(!bounce);
2741
2742 assert!(state.out_msgs.is_empty());
2743 assert_eq!(state.end_lt, prev_end_lt);
2744
2745 assert_eq!(state.total_fees, prev_total_fees);
2746 assert_eq!(state.balance, prev_balance);
2747 }
2748 Ok(())
2749 }
2750
2751 #[test]
2752 fn cant_pay_fwd_fee() -> Result<()> {
2753 let params = make_default_params();
2754 let config = make_default_config();
2755 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, Tokens::new(50000));
2756
2757 let compute_phase = stub_compute_phase(OK_GAS);
2758 let prev_balance = state.balance.clone();
2759 let prev_total_fee = state.total_fees;
2760 let prev_end_lt = state.end_lt;
2761
2762 let actions = make_action_list([OutAction::SendMsg {
2763 mode: SendMsgFlags::PAY_FEE_SEPARATELY,
2764 out_msg: make_relaxed_message(
2765 RelaxedIntMsgInfo {
2766 value: CurrencyCollection::ZERO,
2767 dst: STUB_ADDR.into(),
2768 ..Default::default()
2769 },
2770 None,
2771 Some({
2772 let mut b = CellBuilder::new();
2773 b.store_reference(Cell::empty_cell())?;
2774 b.store_reference(CellBuilder::build_from(0xdeafbeafu32)?)?;
2775 b
2776 }),
2777 ),
2778 }]);
2779
2780 let ActionPhaseFull {
2781 action_phase,
2782 action_fine,
2783 state_exceeds_limits,
2784 bounce,
2785 } = state.action_phase(ActionPhaseContext {
2786 received_message: None,
2787 original_balance: original_balance(&state, &compute_phase),
2788 new_state: StateInit::default(),
2789 actions: actions.clone(),
2790 compute_phase: &compute_phase,
2791 inspector: None,
2792 })?;
2793
2794 assert_eq!(action_phase, ActionPhase {
2795 success: false,
2796 no_funds: true,
2797 result_code: ResultCode::NotEnoughBalance as _,
2798 result_arg: Some(0),
2799 total_actions: 1,
2800 total_action_fees: Some(prev_balance.tokens),
2801 action_list_hash: *actions.repr_hash(),
2802 ..empty_action_phase()
2803 });
2804 assert_eq!(action_fine, prev_balance.tokens);
2805 assert!(!state_exceeds_limits);
2806 assert!(!bounce);
2807
2808 assert_eq!(state.balance, CurrencyCollection::ZERO);
2809 assert_eq!(
2810 state.total_fees,
2811 prev_total_fee + action_phase.total_action_fees.unwrap_or_default()
2812 );
2813 assert!(state.out_msgs.is_empty());
2814 assert_eq!(state.end_lt, prev_end_lt);
2815 Ok(())
2816 }
2817
2818 #[test]
2819 fn rewrite_message() -> Result<()> {
2820 let params = make_default_params();
2821 let config = make_default_config();
2822 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
2823
2824 let compute_phase = stub_compute_phase(OK_GAS);
2825 let prev_balance = state.balance.clone();
2826 let prev_total_fee = state.total_fees;
2827 let prev_end_lt = state.end_lt;
2828
2829 let msg_body = {
2830 let mut b = CellBuilder::new();
2831 b.store_zeros(600)?;
2832 b.store_reference(Cell::empty_cell())?;
2833 b
2834 };
2835
2836 let actions = make_action_list([OutAction::SendMsg {
2837 mode: SendMsgFlags::PAY_FEE_SEPARATELY,
2838 out_msg: make_relaxed_message(
2839 RelaxedIntMsgInfo {
2840 value: CurrencyCollection::ZERO,
2841 dst: STUB_ADDR.into(),
2842 ..Default::default()
2843 },
2844 None,
2845 Some(msg_body.clone()),
2846 ),
2847 }]);
2848
2849 let ActionPhaseFull {
2850 action_phase,
2851 action_fine,
2852 state_exceeds_limits,
2853 bounce,
2854 } = state.action_phase(ActionPhaseContext {
2855 received_message: None,
2856 original_balance: original_balance(&state, &compute_phase),
2857 new_state: StateInit::default(),
2858 actions: actions.clone(),
2859 compute_phase: &compute_phase,
2860 inspector: None,
2861 })?;
2862
2863 assert_eq!(state.out_msgs.len(), 1);
2864 let last_msg = state.out_msgs.last().unwrap();
2865 let msg = last_msg.load()?;
2866 assert_eq!(
2867 msg.layout,
2868 Some(MessageLayout {
2869 init_to_cell: false,
2870 body_to_cell: true,
2871 })
2872 );
2873 assert_eq!(msg.body.1, msg_body.build()?);
2874
2875 let MsgInfo::Int(info) = msg.info else {
2876 panic!("expected an internal message");
2877 };
2878
2879 let expected_fwd_fees = config.fwd_prices.compute_fwd_fee(CellTreeStats {
2880 bit_count: 600,
2881 cell_count: 2,
2882 });
2883 let first_frac = config.fwd_prices.get_first_part(expected_fwd_fees);
2884
2885 assert_eq!(action_phase, ActionPhase {
2886 total_actions: 1,
2887 messages_created: 1,
2888 total_fwd_fees: Some(expected_fwd_fees),
2889 total_action_fees: Some(first_frac),
2890 action_list_hash: *actions.repr_hash(),
2891 total_message_size: compute_full_stats(last_msg, ¶ms),
2892 ..empty_action_phase()
2893 });
2894 assert_eq!(action_fine, Tokens::ZERO);
2895 assert!(!state_exceeds_limits);
2896 assert!(!bounce);
2897
2898 assert_eq!(state.end_lt, prev_end_lt + 1);
2899 assert_eq!(
2900 state.total_fees,
2901 prev_total_fee + action_phase.total_action_fees.unwrap_or_default()
2902 );
2903 assert_eq!(state.balance.other, prev_balance.other);
2904 assert_eq!(
2905 state.balance.tokens,
2906 prev_balance.tokens - info.value.tokens - expected_fwd_fees
2907 );
2908 Ok(())
2909 }
2910
2911 #[test]
2912 fn change_lib() -> Result<()> {
2913 struct TestCase {
2914 libraries: Dict<HashBytes, SimpleLib>,
2915 target: Dict<HashBytes, SimpleLib>,
2916 changes: Vec<(ChangeLibraryMode, LibRef)>,
2917 diff: ahash::HashMap<HashBytes, PublicLibraryChange>,
2918 }
2919
2920 fn make_lib(id: u32) -> Cell {
2921 CellBuilder::build_from(id).unwrap()
2922 }
2923
2924 fn make_lib_hash(id: u32) -> HashBytes {
2925 *CellBuilder::build_from(id).unwrap().repr_hash()
2926 }
2927
2928 fn make_libs<I>(items: I) -> Dict<HashBytes, SimpleLib>
2929 where
2930 I: IntoIterator<Item = (u32, bool)>,
2931 {
2932 let mut items = items
2933 .into_iter()
2934 .map(|(id, public)| {
2935 let root = make_lib(id);
2936 (*root.repr_hash(), SimpleLib { root, public })
2937 })
2938 .collect::<Vec<_>>();
2939 items.sort_by_key(|(a, _)| *a);
2940 Dict::try_from_sorted_slice(&items).unwrap()
2941 }
2942
2943 enum Change {
2944 Add(Cell),
2945 Remove(HashBytes),
2946 }
2947
2948 fn make_diff(
2949 items: impl IntoIterator<Item = Change>,
2950 ) -> ahash::HashMap<HashBytes, PublicLibraryChange> {
2951 let mut result = ahash::HashMap::<_, _>::default();
2952 for item in items {
2953 match item {
2954 Change::Add(cell) => {
2955 result.insert(*cell.repr_hash(), PublicLibraryChange::Add(cell));
2956 }
2957 Change::Remove(hash_bytes) => {
2958 result.insert(hash_bytes, PublicLibraryChange::Remove);
2959 }
2960 }
2961 }
2962 result
2963 }
2964
2965 let params = make_default_params();
2966 let config = make_default_config();
2967
2968 let test_cases = vec![
2969 TestCase {
2971 libraries: Dict::new(),
2972 target: Dict::new(),
2973 changes: Vec::new(),
2974 diff: make_diff([]),
2975 },
2976 TestCase {
2978 libraries: Dict::new(),
2979 target: make_libs([(123, false)]),
2980 changes: vec![(ChangeLibraryMode::ADD_PRIVATE, LibRef::Cell(make_lib(123)))],
2981 diff: make_diff([]),
2982 },
2983 TestCase {
2984 libraries: Dict::new(),
2985 target: make_libs([(123, false), (234, false)]),
2986 changes: vec![
2987 (ChangeLibraryMode::ADD_PRIVATE, LibRef::Cell(make_lib(123))),
2988 (ChangeLibraryMode::ADD_PRIVATE, LibRef::Cell(make_lib(234))),
2989 ],
2990 diff: make_diff([]),
2991 },
2992 TestCase {
2993 libraries: make_libs([(123, false)]),
2994 target: make_libs([(123, false)]),
2995 changes: vec![(ChangeLibraryMode::ADD_PRIVATE, LibRef::Cell(make_lib(123)))],
2996 diff: make_diff([]),
2997 },
2998 TestCase {
2999 libraries: make_libs([(123, false)]),
3000 target: make_libs([(123, false)]),
3001 changes: vec![(
3002 ChangeLibraryMode::ADD_PRIVATE,
3003 LibRef::Hash(make_lib_hash(123)),
3004 )],
3005 diff: make_diff([]),
3006 },
3007 TestCase {
3009 libraries: Dict::new(),
3010 target: make_libs([(123, true)]),
3011 changes: vec![(ChangeLibraryMode::ADD_PUBLIC, LibRef::Cell(make_lib(123)))],
3012 diff: make_diff([Change::Add(make_lib(123))]),
3013 },
3014 TestCase {
3015 libraries: Dict::new(),
3016 target: make_libs([(123, true), (234, true)]),
3017 changes: vec![
3018 (ChangeLibraryMode::ADD_PUBLIC, LibRef::Cell(make_lib(123))),
3019 (ChangeLibraryMode::ADD_PUBLIC, LibRef::Cell(make_lib(234))),
3020 ],
3021 diff: make_diff([Change::Add(make_lib(123)), Change::Add(make_lib(234))]),
3022 },
3023 TestCase {
3024 libraries: make_libs([(123, true)]),
3025 target: make_libs([(123, true)]),
3026 changes: vec![(ChangeLibraryMode::ADD_PUBLIC, LibRef::Cell(make_lib(123)))],
3027 diff: make_diff([]),
3028 },
3029 TestCase {
3030 libraries: make_libs([(123, true)]),
3031 target: make_libs([(123, true)]),
3032 changes: vec![(
3033 ChangeLibraryMode::ADD_PUBLIC,
3034 LibRef::Hash(make_lib_hash(123)),
3035 )],
3036 diff: make_diff([]),
3037 },
3038 TestCase {
3040 libraries: Dict::new(),
3041 target: make_libs([(234, true)]),
3042 changes: vec![
3043 (ChangeLibraryMode::ADD_PUBLIC, LibRef::Cell(make_lib(234))),
3044 (ChangeLibraryMode::ADD_PUBLIC, LibRef::Cell(make_lib(123))),
3045 (ChangeLibraryMode::REMOVE, LibRef::Hash(make_lib_hash(123))),
3046 ],
3047 diff: make_diff([Change::Add(make_lib(234))]),
3048 },
3049 TestCase {
3050 libraries: make_libs([(123, true)]),
3051 target: make_libs([(123, true), (234, true)]),
3052 changes: vec![
3053 (ChangeLibraryMode::ADD_PUBLIC, LibRef::Cell(make_lib(234))),
3054 (ChangeLibraryMode::REMOVE, LibRef::Hash(make_lib_hash(123))),
3055 (ChangeLibraryMode::ADD_PUBLIC, LibRef::Cell(make_lib(123))),
3056 ],
3057 diff: make_diff([Change::Add(make_lib(234))]),
3058 },
3059 TestCase {
3061 libraries: Dict::new(),
3062 target: Dict::new(),
3063 changes: vec![(ChangeLibraryMode::REMOVE, LibRef::Cell(make_lib(123)))],
3065 diff: make_diff([]),
3066 },
3067 TestCase {
3068 libraries: Dict::new(),
3069 target: Dict::new(),
3070 changes: vec![(ChangeLibraryMode::REMOVE, LibRef::Hash(make_lib_hash(123)))],
3072 diff: make_diff([]),
3073 },
3074 TestCase {
3075 libraries: make_libs([(123, false)]),
3076 target: Dict::new(),
3077 changes: vec![(ChangeLibraryMode::REMOVE, LibRef::Cell(make_lib(123)))],
3078 diff: make_diff([]),
3079 },
3080 TestCase {
3081 libraries: make_libs([(123, false)]),
3082 target: Dict::new(),
3083 changes: vec![(ChangeLibraryMode::REMOVE, LibRef::Hash(make_lib_hash(123)))],
3084 diff: make_diff([]),
3085 },
3086 TestCase {
3087 libraries: make_libs([(123, true)]),
3088 target: Dict::new(),
3089 changes: vec![(ChangeLibraryMode::REMOVE, LibRef::Cell(make_lib(123)))],
3090 diff: make_diff([Change::Remove(make_lib_hash(123))]),
3091 },
3092 TestCase {
3094 libraries: make_libs([(123, false)]),
3095 target: make_libs([(123, true)]),
3096 changes: vec![(ChangeLibraryMode::ADD_PUBLIC, LibRef::Cell(make_lib(123)))],
3097 diff: make_diff([Change::Add(make_lib(123))]),
3098 },
3099 TestCase {
3100 libraries: make_libs([(123, false)]),
3101 target: make_libs([(123, true)]),
3102 changes: vec![(
3103 ChangeLibraryMode::ADD_PUBLIC,
3104 LibRef::Hash(make_lib_hash(123)),
3105 )],
3106 diff: make_diff([Change::Add(make_lib(123))]),
3107 },
3108 TestCase {
3109 libraries: make_libs([(123, true)]),
3110 target: make_libs([(123, false)]),
3111 changes: vec![(ChangeLibraryMode::ADD_PRIVATE, LibRef::Cell(make_lib(123)))],
3112 diff: make_diff([Change::Remove(make_lib_hash(123))]),
3113 },
3114 TestCase {
3115 libraries: make_libs([(123, true)]),
3116 target: make_libs([(123, false)]),
3117 changes: vec![(
3118 ChangeLibraryMode::ADD_PRIVATE,
3119 LibRef::Hash(make_lib_hash(123)),
3120 )],
3121 diff: make_diff([Change::Remove(make_lib_hash(123))]),
3122 },
3123 ];
3124
3125 for TestCase {
3126 libraries,
3127 target,
3128 changes,
3129 diff,
3130 } in test_cases
3131 {
3132 let mut state = ExecutorState::new_active(
3133 ¶ms,
3134 &config,
3135 &StdAddr::new(-1, HashBytes::ZERO),
3136 OK_BALANCE,
3137 Cell::empty_cell(),
3138 tvmasm!("ACCEPT"),
3139 );
3140
3141 let target_state_init = match &mut state.state {
3142 AccountState::Active(state_init) => {
3143 state_init.libraries = libraries;
3144 StateInit {
3145 libraries: target,
3146 ..state_init.clone()
3147 }
3148 }
3149 AccountState::Uninit | AccountState::Frozen(..) => panic!("invalid initial state"),
3150 };
3151
3152 let compute_phase = stub_compute_phase(OK_GAS);
3153 let prev_total_fees = state.total_fees;
3154 let prev_balance = state.balance.clone();
3155
3156 let change_count = changes.len();
3157 let actions = make_action_list(
3158 changes
3159 .into_iter()
3160 .map(|(mode, lib)| OutAction::ChangeLibrary { mode, lib }),
3161 );
3162
3163 let mut inspector = ExecutorInspector::default();
3164
3165 let ActionPhaseFull {
3166 action_phase,
3167 action_fine,
3168 state_exceeds_limits,
3169 bounce,
3170 } = state.action_phase(ActionPhaseContext {
3171 received_message: None,
3172 original_balance: original_balance(&state, &compute_phase),
3173 new_state: match state.state.clone() {
3174 AccountState::Active(state_init) => state_init,
3175 AccountState::Uninit | AccountState::Frozen(..) => Default::default(),
3176 },
3177 actions: actions.clone(),
3178 compute_phase: &compute_phase,
3179 inspector: Some(&mut inspector),
3180 })?;
3181
3182 assert_eq!(action_phase, ActionPhase {
3183 total_actions: change_count as u16,
3184 special_actions: change_count as u16,
3185 action_list_hash: *actions.repr_hash(),
3186 ..empty_action_phase()
3187 });
3188 assert_eq!(action_fine, Tokens::ZERO);
3189 assert!(!state_exceeds_limits);
3190 assert!(!bounce);
3191 assert_eq!(state.end_status, AccountStatus::Active);
3192 assert_eq!(state.state, AccountState::Active(target_state_init));
3193 assert_eq!(state.total_fees, prev_total_fees);
3194 assert_eq!(state.balance, prev_balance);
3195 assert_eq!(inspector.public_libs_diff, diff);
3196 }
3197 Ok(())
3198 }
3199}