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