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