1use anyhow::{Context, anyhow};
2use tycho_types::models::{
3 AccountStatus, BounceReason, ComputePhase, NewBounceComputePhaseInfo, OrdinaryTxInfo,
4};
5use tycho_types::num::Tokens;
6use tycho_types::prelude::*;
7
8use crate::error::{TxError, TxResult};
9use crate::phase::{
10 ActionPhaseContext, BouncePhaseContext, ComputePhaseContext, ComputePhaseFull,
11 StoragePhaseContext, TransactionInput,
12};
13use crate::{ExecutorInspector, ExecutorState};
14
15impl ExecutorState<'_> {
16 pub fn run_ordinary_transaction(
17 &mut self,
18 is_external: bool,
19 msg_root: Cell,
20 mut inspector: Option<&mut ExecutorInspector<'_>>,
21 ) -> TxResult<OrdinaryTxInfo> {
22 let mut msg = match self.receive_in_msg(msg_root) {
24 Ok(msg) if msg.is_external == is_external => msg,
25 Ok(_) => {
26 return Err(TxError::Fatal(anyhow!(
27 "received an unexpected inbound message"
28 )));
29 }
30 Err(_) if is_external => return Err(TxError::Skipped),
32 Err(e) => return Err(TxError::Fatal(e)),
33 };
34
35 let storage_phase;
38 let credit_phase;
39 if msg.bounce_enabled {
40 storage_phase = self
42 .storage_phase(StoragePhaseContext {
43 adjust_msg_balance: false,
44 received_message: Some(&mut msg),
45 })
46 .context("storage phase failed")?;
47
48 credit_phase = if is_external {
50 None
51 } else {
52 Some(self.credit_phase(&msg).context("credit phase failed")?)
53 };
54 } else {
55 credit_phase = if is_external {
57 None
58 } else {
59 Some(self.credit_phase(&msg).context("credit phase failed")?)
60 };
61
62 storage_phase = self
64 .storage_phase(StoragePhaseContext {
65 adjust_msg_balance: true,
66 received_message: Some(&mut msg),
67 })
68 .context("storage phase failed")?;
69 }
70
71 let ComputePhaseFull {
73 compute_phase,
74 accepted,
75 original_balance,
76 new_state,
77 actions,
78 } = self
79 .compute_phase(ComputePhaseContext {
80 input: TransactionInput::Ordinary(&msg),
81 storage_fee: storage_phase.storage_fees_collected,
82 force_accept: false,
83 stop_on_accept: false,
84 inspector: inspector.as_deref_mut(),
85 })
86 .context("compute phase failed")?;
87
88 if is_external && !accepted {
89 return Err(TxError::Skipped);
90 }
91
92 let mut aborted = true;
94 let mut state_exceeds_limits = false;
95 let mut bounce_required = false;
96 let mut action_fine = Tokens::ZERO;
97 let mut destroyed = false;
98
99 let mut action_phase = None;
100 if let ComputePhase::Executed(compute_phase) = &compute_phase
101 && compute_phase.success
102 {
103 let res = self
104 .action_phase(ActionPhaseContext {
105 received_message: Some(&mut msg),
106 original_balance,
107 new_state,
108 actions,
109 compute_phase,
110 inspector,
111 })
112 .context("action phase failed")?;
113
114 aborted = !res.action_phase.success;
115 state_exceeds_limits = res.state_exceeds_limits;
116 bounce_required = res.bounce;
117 action_fine = res.action_fine;
118 destroyed = self.end_status == AccountStatus::NotExists;
119
120 action_phase = Some(res.action_phase);
121 }
122
123 let mut bounce_phase = None;
125 if msg.bounce_enabled
126 && (!matches!(&compute_phase, ComputePhase::Executed(p) if p.success)
127 || state_exceeds_limits
128 || bounce_required)
129 {
130 debug_assert!(!is_external);
131
132 let reason = if let Some(phase) = &action_phase {
133 BounceReason::ActionPhaseFailed {
134 result_code: phase.result_code,
135 }
136 } else {
137 match &compute_phase {
138 ComputePhase::Executed(phase) => BounceReason::ComputePhaseFailed {
139 exit_code: phase.exit_code,
140 },
141 ComputePhase::Skipped(skipped) => {
142 BounceReason::ComputePhaseSkipped(skipped.reason)
143 }
144 }
145 };
146
147 let (gas_fees, compute_phase_info) = match &compute_phase {
148 ComputePhase::Executed(phase) => (
149 phase.gas_fees,
150 Some(NewBounceComputePhaseInfo {
151 gas_used: phase.gas_used.into_inner().try_into().unwrap_or(u32::MAX),
152 vm_steps: phase.vm_steps,
153 }),
154 ),
155 ComputePhase::Skipped(_) => (Tokens::ZERO, None),
156 };
157
158 bounce_phase = Some(
159 self.bounce_phase(BouncePhaseContext {
160 gas_fees,
161 action_fine,
162 received_message: &msg,
163 reason,
164 compute_phase_info,
165 })
166 .context("bounce phase failed")?,
167 );
168 }
169
170 Ok(OrdinaryTxInfo {
172 credit_first: !msg.bounce_enabled,
173 storage_phase: Some(storage_phase),
174 credit_phase,
175 compute_phase,
176 action_phase,
177 aborted,
178 bounce_phase,
179 destroyed,
180 })
181 }
182
183 pub fn check_ordinary_transaction(
184 &mut self,
185 msg_root: Cell,
186 inspector: Option<&mut ExecutorInspector<'_>>,
187 ) -> TxResult<()> {
188 let mut msg = match self.receive_in_msg(msg_root) {
190 Ok(msg) if msg.is_external => msg,
191 _ => return Err(TxError::Skipped),
192 };
193
194 let storage_phase = self
196 .storage_phase(StoragePhaseContext {
197 adjust_msg_balance: false,
198 received_message: Some(&mut msg),
199 })
200 .context("storage phase failed")?;
201
202 let ComputePhaseFull { accepted, .. } = self
204 .compute_phase(ComputePhaseContext {
205 input: TransactionInput::Ordinary(&msg),
206 storage_fee: storage_phase.storage_fees_collected,
207 force_accept: false,
208 stop_on_accept: true,
209 inspector,
210 })
211 .context("compute phase failed")?;
212
213 if !accepted {
214 return Err(TxError::Skipped);
215 }
216
217 Ok(())
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use anyhow::Result;
224 use tycho_asm_macros::tvmasm;
225 use tycho_types::cell::Lazy;
226 use tycho_types::models::{
227 Account, AccountState, AccountStatusChange, CurrencyCollection, ExtInMsgInfo, IntMsgInfo,
228 MsgInfo, OptionalAccount, ShardAccount, StateInit, StdAddr, StorageInfo, StorageUsed,
229 TxInfo,
230 };
231 use tycho_types::num::VarUint56;
232
233 use super::*;
234 use crate::Executor;
235 use crate::tests::{make_default_config, make_default_params, make_message};
236
237 const STUB_ADDR: StdAddr = StdAddr::new(0, HashBytes::ZERO);
238
239 pub fn make_uninit_with_balance<T: Into<CurrencyCollection>>(
240 address: &StdAddr,
241 balance: T,
242 ) -> ShardAccount {
243 ShardAccount {
244 account: Lazy::new(&OptionalAccount(Some(Account {
245 address: address.clone().into(),
246 storage_stat: StorageInfo::default(),
247 last_trans_lt: 1001,
248 balance: balance.into(),
249 state: AccountState::Uninit,
250 })))
251 .unwrap(),
252 last_trans_hash: HashBytes([0x11; 32]),
253 last_trans_lt: 1000,
254 }
255 }
256
257 #[test]
258 fn ever_wallet_deploys() -> Result<()> {
259 let config = make_default_config();
260 let params = make_default_params();
261
262 let code = Boc::decode(include_bytes!("../../res/ever_wallet_code.boc"))?;
263 let data = CellBuilder::build_from((HashBytes::ZERO, 0u64))?;
264
265 let state_init = StateInit {
266 split_depth: None,
267 special: None,
268 code: Some(code),
269 data: Some(data),
270 libraries: Dict::new(),
271 };
272 let address = StdAddr::new(0, *CellBuilder::build_from(&state_init)?.repr_hash());
273
274 let msg = make_message(
275 ExtInMsgInfo {
276 src: None,
277 dst: address.clone().into(),
278 import_fee: Tokens::ZERO,
279 },
280 Some(state_init),
281 Some({
282 let mut b = CellBuilder::new();
283 b.store_bit_one()?;
285 b.store_u256(&HashBytes::ZERO)?;
286 b.store_u256(&HashBytes::ZERO)?;
287 b.store_bit_one()?;
289 b.store_zeros(256)?;
290 b.store_u64((params.block_unixtime - 10) as u64 * 1000)?;
292 b.store_u32(params.block_unixtime + 40)?;
294 b.store_u32(0x4cee646c)?;
296 b.store_reference({
298 let mut b = CellBuilder::new();
299 address.store_into(&mut b, Cell::empty_context())?;
301 b.store_u128(10000000)?;
303 b.store_bit_zero()?;
305 b.store_u8(0b11)?;
307 b.store_reference(Cell::empty_cell())?;
309 b.build()?
311 })?;
312 b
314 }),
315 );
316
317 let state = make_uninit_with_balance(&address, CurrencyCollection::new(1_000_000_000));
318
319 let output = Executor::new(¶ms, config.as_ref())
320 .begin_ordinary(&address, true, &msg, &state)?
321 .commit()?;
322
323 println!("SHARD_STATE: {:#?}", output.new_state);
324 let account = output.new_state.load_account()?;
325 println!("ACCOUNT: {:#?}", account);
326
327 let tx = output.transaction.load()?;
328 println!("TX: {tx:#?}");
329 let info = tx.load_info()?;
330 println!("INFO: {info:#?}");
331
332 Ok(())
333 }
334
335 #[test]
336 fn freeze_account() -> Result<()> {
337 let params = make_default_params();
338 let config = make_default_config();
339
340 let code = tvmasm!(
341 r#"
342 NEWC INT 123 STUR 32 ENDC
343 POP c4
344 ACCEPT
345 "#
346 );
347 let mut state = ExecutorState::new_active(
348 ¶ms,
349 &config,
350 &STUB_ADDR,
351 Tokens::ZERO,
352 CellBuilder::build_from(u32::MIN)?,
353 code,
354 );
355 state.storage_stat = StorageInfo {
356 used: StorageUsed {
357 bits: VarUint56::new(128),
358 cells: VarUint56::new(1),
359 },
360 storage_extra: Default::default(),
361 last_paid: params.block_unixtime - 1000,
362 due_payment: Some(Tokens::new(2 * config.gas_prices.freeze_due_limit as u128)),
363 };
364
365 let info = state.run_ordinary_transaction(
366 false,
367 make_message(
368 IntMsgInfo {
369 src: STUB_ADDR.into(),
370 dst: STUB_ADDR.into(),
371 value: CurrencyCollection::new(1_000_000),
372 bounce: true,
373 ..Default::default()
374 },
375 None,
376 None,
377 ),
378 None,
379 )?;
380
381 assert!(!info.aborted);
382 assert_eq!(
383 info.storage_phase.unwrap().status_change,
384 AccountStatusChange::Frozen
385 );
386
387 let ComputePhase::Executed(compute_phase) = info.compute_phase else {
388 panic!("expected an executed compute phase");
389 };
390 assert!(compute_phase.success);
391
392 let action_phase = info.action_phase.unwrap();
393 assert!(action_phase.success);
394 assert_eq!(action_phase.messages_created, 0);
395
396 assert_eq!(info.bounce_phase, None);
397
398 assert_eq!(state.orig_status, AccountStatus::Active);
399 assert_eq!(state.end_status, AccountStatus::Frozen);
400 assert_eq!(state.balance, CurrencyCollection::ZERO);
401
402 assert_eq!(
403 state.state,
404 AccountState::Active(StateInit {
405 code: Boc::decode(code).map(Some)?,
406 data: CellBuilder::build_from(123u32).map(Some)?,
407 ..Default::default()
408 })
409 );
410
411 Ok(())
412 }
413
414 #[test]
415 fn deploy_delete_in_same_tx() -> Result<()> {
416 let params = make_default_params();
417 let config = make_default_config();
418
419 let code = Boc::decode(tvmasm!(
420 r#"
421 ACCEPT
422 NEWC
423 // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
424 INT 0b011000 STUR 6
425 MYADDR
426 STSLICER
427 INT 0 STGRAMS
428 // extra:$0 ihr_fee:Tokens fwd_fee:Tokens created_lt:uint64 created_at:uint32
429 // 1 + 4 + 4 + 64 + 32
430 // init:none$0 body:left$0
431 // 1 + 1
432 INT 107 STZEROES
433 ENDC INT 160 SENDRAWMSG
434 "#
435 ))?;
436 let state_init = StateInit {
437 code: Some(code),
438 ..Default::default()
439 };
440
441 let address = StdAddr::new(0, *CellBuilder::build_from(&state_init)?.repr_hash());
442
443 let msg_balance = Tokens::new(1_000_000_000);
444 let msg = make_message(
445 IntMsgInfo {
446 src: address.clone().into(),
447 dst: address.clone().into(),
448 value: msg_balance.into(),
449 ..Default::default()
450 },
451 Some(state_init),
452 None,
453 );
454
455 let state = ShardAccount {
456 account: Lazy::new(&OptionalAccount::EMPTY)?,
457 last_trans_hash: HashBytes::ZERO,
458 last_trans_lt: 0,
459 };
460
461 let output = Executor::new(¶ms, config.as_ref())
462 .begin_ordinary(&address, false, msg, &state)?
463 .commit()?;
464
465 let new_account_state = output.new_state.load_account()?;
466 assert_eq!(new_account_state, None);
467
468 let tx = output.transaction.load()?;
469 assert_eq!(tx.orig_status, AccountStatus::NotExists);
470 assert_eq!(tx.end_status, AccountStatus::NotExists);
471
472 let TxInfo::Ordinary(info) = tx.load_info()? else {
473 panic!("expected an ordinary transaction info");
474 };
475 println!("{info:#?}");
476
477 assert!(!info.aborted);
478 assert!(info.destroyed);
479
480 let ComputePhase::Executed(compute_phase) = info.compute_phase else {
481 panic!("expected an executed compute phase");
482 };
483 assert!(compute_phase.success);
484 let action_phase = info.action_phase.unwrap();
485 assert!(action_phase.success);
486 assert_eq!(action_phase.total_actions, 1);
487 assert_eq!(action_phase.messages_created, 1);
488
489 assert_eq!(output.transaction_meta.out_msgs.len(), 1);
490 let out_msg = output.transaction_meta.out_msgs.last().unwrap().load()?;
491
492 {
493 let MsgInfo::Int(info) = out_msg.info else {
494 panic!("expected an internal outbound message");
495 };
496
497 assert_eq!(info.src, address.clone().into());
498 assert_eq!(info.dst, address.clone().into());
499 assert!(info.value.other.is_empty());
500 assert_eq!(
501 info.value.tokens,
502 msg_balance
503 - compute_phase.gas_fees
504 - action_phase.total_fwd_fees.unwrap_or_default()
505 );
506
507 assert_eq!(out_msg.init, None);
508 assert_eq!(out_msg.body, Default::default());
509 }
510
511 Ok(())
512 }
513}