1#[cfg(feature = "alloc")]
46#[macro_export]
47macro_rules! script_with_data_offset {
48 ($offset:ident, $script:expr, $tx_offset:expr) => {{
49 let $offset = {
50 let $offset = {
52 use $crate::prelude::Immediate18;
53 0 as Immediate18
54 };
55 let script_bytes: $crate::alloc::vec::Vec<u8> =
57 ::core::iter::IntoIterator::into_iter({ $script }).collect();
58 {
60 use $crate::{
61 fuel_tx::{
62 field::Script as ScriptField,
63 Script,
64 },
65 fuel_types::bytes::padded_len,
66 prelude::Immediate18,
67 };
68 let value: Immediate18 = $tx_offset
69 .saturating_add(Script::script_offset_static())
70 .saturating_add(
71 padded_len(script_bytes.as_slice()).unwrap_or(usize::MAX),
72 )
73 .try_into()
74 .expect("script data offset is too large");
75 value
76 }
77 };
78 ($script, $offset)
81 }};
82}
83
84#[allow(missing_docs)]
85#[cfg(feature = "random")]
86#[cfg(any(test, feature = "test-helpers"))]
87pub mod test_helpers {
89 use alloc::{
90 vec,
91 vec::Vec,
92 };
93
94 use crate::{
95 checked_transaction::{
96 builder::TransactionBuilderExt,
97 Checked,
98 IntoChecked,
99 },
100 interpreter::Memory,
101 memory_client::MemoryClient,
102 state::StateTransition,
103 storage::{
104 ContractsAssetsStorage,
105 MemoryStorage,
106 },
107 transactor::Transactor,
108 };
109 use anyhow::anyhow;
110
111 use crate::{
112 interpreter::{
113 CheckedMetadata,
114 ExecutableTransaction,
115 InterpreterParams,
116 MemoryInstance,
117 },
118 prelude::{
119 Backtrace,
120 Call,
121 },
122 };
123 use fuel_asm::{
124 op,
125 GTFArgs,
126 Instruction,
127 PanicReason,
128 RegId,
129 };
130 use fuel_tx::{
131 field::{
132 Outputs,
133 ReceiptsRoot,
134 },
135 BlobBody,
136 BlobIdExt,
137 ConsensusParameters,
138 Contract,
139 ContractParameters,
140 Create,
141 FeeParameters,
142 Finalizable,
143 GasCosts,
144 Input,
145 Output,
146 PredicateParameters,
147 Receipt,
148 Script,
149 ScriptParameters,
150 StorageSlot,
151 Transaction,
152 TransactionBuilder,
153 TxParameters,
154 Witness,
155 };
156 use fuel_types::{
157 canonical::{
158 Deserialize,
159 Serialize,
160 },
161 Address,
162 AssetId,
163 BlobId,
164 BlockHeight,
165 ChainId,
166 ContractId,
167 Immediate12,
168 Salt,
169 Word,
170 };
171 use itertools::Itertools;
172 use rand::{
173 prelude::StdRng,
174 Rng,
175 SeedableRng,
176 };
177
178 pub struct CreatedContract {
179 pub tx: Create,
180 pub contract_id: ContractId,
181 pub salt: Salt,
182 }
183
184 pub struct TestBuilder {
185 pub rng: StdRng,
186 gas_price: Word,
187 max_fee_limit: Word,
188 script_gas_limit: Word,
189 builder: TransactionBuilder<Script>,
190 storage: MemoryStorage,
191 block_height: BlockHeight,
192 consensus_params: ConsensusParameters,
193 }
194
195 impl TestBuilder {
196 pub fn new(seed: u64) -> Self {
197 let bytecode = core::iter::once(op::ret(RegId::ONE)).collect();
198 TestBuilder {
199 rng: StdRng::seed_from_u64(seed),
200 gas_price: 0,
201 max_fee_limit: 0,
202 script_gas_limit: 100,
203 builder: TransactionBuilder::script(bytecode, vec![]),
204 storage: MemoryStorage::default(),
205 block_height: Default::default(),
206 consensus_params: ConsensusParameters::standard(),
207 }
208 }
209
210 pub fn get_block_height(&self) -> BlockHeight {
211 self.block_height
212 }
213
214 pub fn start_script_bytes(
215 &mut self,
216 script: Vec<u8>,
217 script_data: Vec<u8>,
218 ) -> &mut Self {
219 self.start_script_inner(script, script_data)
220 }
221
222 pub fn start_script(
223 &mut self,
224 script: Vec<Instruction>,
225 script_data: Vec<u8>,
226 ) -> &mut Self {
227 let script = script.into_iter().collect();
228 self.start_script_inner(script, script_data)
229 }
230
231 fn start_script_inner(
232 &mut self,
233 script: Vec<u8>,
234 script_data: Vec<u8>,
235 ) -> &mut Self {
236 self.builder = TransactionBuilder::script(script, script_data);
237 self.builder.script_gas_limit(self.script_gas_limit);
238 self
239 }
240
241 pub fn gas_price(&mut self, price: Word) -> &mut TestBuilder {
242 self.gas_price = price;
243 self
244 }
245
246 pub fn max_fee_limit(&mut self, max_fee_limit: Word) -> &mut TestBuilder {
247 self.max_fee_limit = max_fee_limit;
248 self
249 }
250
251 pub fn script_gas_limit(&mut self, limit: Word) -> &mut TestBuilder {
252 self.builder.script_gas_limit(limit);
253 self.script_gas_limit = limit;
254 self
255 }
256
257 pub fn change_output(&mut self, asset_id: AssetId) -> &mut TestBuilder {
258 self.builder
259 .add_output(Output::change(self.rng.gen(), 0, asset_id));
260 self
261 }
262
263 pub fn coin_output(
264 &mut self,
265 asset_id: AssetId,
266 amount: Word,
267 ) -> &mut TestBuilder {
268 self.builder
269 .add_output(Output::coin(self.rng.gen(), amount, asset_id));
270 self
271 }
272
273 pub fn variable_output(&mut self, asset_id: AssetId) -> &mut TestBuilder {
274 self.builder
275 .add_output(Output::variable(Address::zeroed(), 0, asset_id));
276 self
277 }
278
279 pub fn contract_output(&mut self, id: &ContractId) -> &mut TestBuilder {
280 let input_idx = self
281 .builder
282 .inputs()
283 .iter()
284 .find_position(|input| matches!(input, Input::Contract(contract) if &contract.contract_id == id))
285 .expect("expected contract input with matching contract id");
286
287 self.builder.add_output(Output::contract(
288 u16::try_from(input_idx.0).expect("The input index is more than allowed"),
289 self.rng.gen(),
290 self.rng.gen(),
291 ));
292
293 self
294 }
295
296 pub fn coin_input(
297 &mut self,
298 asset_id: AssetId,
299 amount: Word,
300 ) -> &mut TestBuilder {
301 self.builder.add_unsigned_coin_input(
302 fuel_crypto::SecretKey::random(&mut self.rng),
303 self.rng.gen(),
304 amount,
305 asset_id,
306 Default::default(),
307 );
308 self
309 }
310
311 pub fn fee_input(&mut self) -> &mut TestBuilder {
312 self.builder.add_fee_input();
313 self
314 }
315
316 pub fn contract_input(&mut self, contract_id: ContractId) -> &mut TestBuilder {
317 self.builder.add_input(Input::contract(
318 self.rng.gen(),
319 self.rng.gen(),
320 self.rng.gen(),
321 self.rng.gen(),
322 contract_id,
323 ));
324 self
325 }
326
327 pub fn witness(&mut self, witness: Witness) -> &mut TestBuilder {
328 self.builder.add_witness(witness);
329 self
330 }
331
332 pub fn storage(&mut self, storage: MemoryStorage) -> &mut TestBuilder {
333 self.storage = storage;
334 self
335 }
336
337 pub fn block_height(&mut self, block_height: BlockHeight) -> &mut TestBuilder {
338 self.block_height = block_height;
339 self
340 }
341
342 pub fn with_fee_params(&mut self, fee_params: FeeParameters) -> &mut TestBuilder {
343 self.consensus_params.set_fee_params(fee_params);
344 self
345 }
346
347 pub fn with_free_gas_costs(&mut self) -> &mut TestBuilder {
348 let gas_costs = GasCosts::free();
349 self.consensus_params.set_gas_costs(gas_costs);
350 self
351 }
352
353 pub fn base_asset_id(&mut self, base_asset_id: AssetId) -> &mut TestBuilder {
354 self.consensus_params.set_base_asset_id(base_asset_id);
355 self
356 }
357
358 pub fn build(&mut self) -> Checked<Script> {
359 self.builder.max_fee_limit(self.max_fee_limit);
360 self.builder.with_tx_params(*self.get_tx_params());
361 self.builder
362 .with_contract_params(*self.get_contract_params());
363 self.builder
364 .with_predicate_params(*self.get_predicate_params());
365 self.builder.with_script_params(*self.get_script_params());
366 self.builder.with_fee_params(*self.get_fee_params());
367 self.builder.with_base_asset_id(*self.get_base_asset_id());
368 self.builder
369 .finalize_checked_with_storage(self.block_height, &self.storage)
370 }
371
372 pub fn get_tx_params(&self) -> &TxParameters {
373 self.consensus_params.tx_params()
374 }
375
376 pub fn get_predicate_params(&self) -> &PredicateParameters {
377 self.consensus_params.predicate_params()
378 }
379
380 pub fn get_script_params(&self) -> &ScriptParameters {
381 self.consensus_params.script_params()
382 }
383
384 pub fn get_contract_params(&self) -> &ContractParameters {
385 self.consensus_params.contract_params()
386 }
387
388 pub fn get_fee_params(&self) -> &FeeParameters {
389 self.consensus_params.fee_params()
390 }
391
392 pub fn get_base_asset_id(&self) -> &AssetId {
393 self.consensus_params.base_asset_id()
394 }
395
396 pub fn get_block_gas_limit(&self) -> u64 {
397 self.consensus_params.block_gas_limit()
398 }
399
400 pub fn get_block_transaction_size_limit(&self) -> u64 {
401 self.consensus_params.block_transaction_size_limit()
402 }
403
404 pub fn get_privileged_address(&self) -> &Address {
405 self.consensus_params.privileged_address()
406 }
407
408 pub fn get_chain_id(&self) -> ChainId {
409 self.consensus_params.chain_id()
410 }
411
412 pub fn get_gas_costs(&self) -> &GasCosts {
413 self.consensus_params.gas_costs()
414 }
415
416 pub fn build_get_balance_tx(
417 contract_id: &ContractId,
418 asset_id: &AssetId,
419 tx_offset: usize,
420 ) -> Checked<Script> {
421 let (script, _) = script_with_data_offset!(
422 data_offset,
423 vec![
424 op::movi(0x11, data_offset),
425 op::addi(
426 0x12,
427 0x11,
428 Immediate12::try_from(AssetId::LEN)
429 .expect("`AssetId::LEN` is 32 bytes")
430 ),
431 op::bal(0x10, 0x11, 0x12),
432 op::log(0x10, RegId::ZERO, RegId::ZERO, RegId::ZERO),
433 op::ret(RegId::ONE),
434 ],
435 tx_offset
436 );
437
438 let script_data: Vec<u8> = [asset_id.as_ref(), contract_id.as_ref()]
439 .into_iter()
440 .flatten()
441 .copied()
442 .collect();
443
444 TestBuilder::new(2322u64)
445 .start_script(script, script_data)
446 .gas_price(0)
447 .script_gas_limit(1_000_000)
448 .contract_input(*contract_id)
449 .fee_input()
450 .contract_output(contract_id)
451 .build()
452 }
453
454 pub fn setup_contract_bytes(
455 &mut self,
456 contract: Vec<u8>,
457 initial_balance: Option<(AssetId, Word)>,
458 initial_state: Option<Vec<StorageSlot>>,
459 ) -> CreatedContract {
460 self.setup_contract_inner(contract, initial_balance, initial_state)
461 }
462
463 pub fn setup_contract(
464 &mut self,
465 contract: Vec<Instruction>,
466 initial_balance: Option<(AssetId, Word)>,
467 initial_state: Option<Vec<StorageSlot>>,
468 ) -> CreatedContract {
469 let contract = contract.into_iter().collect();
470
471 self.setup_contract_inner(contract, initial_balance, initial_state)
472 }
473
474 fn setup_contract_inner(
475 &mut self,
476 contract: Vec<u8>,
477 initial_balance: Option<(AssetId, Word)>,
478 initial_state: Option<Vec<StorageSlot>>,
479 ) -> CreatedContract {
480 let storage_slots = initial_state.unwrap_or_default();
481
482 let salt: Salt = self.rng.gen();
483 let program: Witness = contract.into();
484 let storage_root = Contract::initial_state_root(storage_slots.iter());
485 let contract = Contract::from(program.as_ref());
486 let contract_root = contract.root();
487 let contract_id = contract.id(&salt, &contract_root, &storage_root);
488
489 let tx = TransactionBuilder::create(program, salt, storage_slots)
490 .max_fee_limit(self.max_fee_limit)
491 .maturity(Default::default())
492 .add_fee_input()
493 .add_contract_created()
494 .finalize()
495 .into_checked(self.block_height, &self.consensus_params)
496 .expect("failed to check tx");
497
498 let state = self
500 .deploy(tx)
501 .expect("Expected vm execution to be successful");
502
503 if let Some((asset_id, amount)) = initial_balance {
505 self.storage
506 .contract_asset_id_balance_insert(&contract_id, &asset_id, amount)
507 .unwrap();
508 }
509
510 CreatedContract {
511 tx: state.tx().clone(),
512 contract_id,
513 salt,
514 }
515 }
516
517 pub fn setup_blob(&mut self, data: Vec<u8>) {
518 let id = BlobId::compute(data.as_slice());
519
520 let tx = TransactionBuilder::blob(BlobBody {
521 id,
522 witness_index: 0,
523 })
524 .add_witness(data.into())
525 .max_fee_limit(self.max_fee_limit)
526 .maturity(Default::default())
527 .add_fee_input()
528 .finalize()
529 .into_checked(self.block_height, &self.consensus_params)
530 .expect("failed to check tx");
531
532 let interpreter_params =
533 InterpreterParams::new(self.gas_price, &self.consensus_params);
534 let mut transactor = Transactor::<_, _, _>::new(
535 MemoryInstance::new(),
536 self.storage.clone(),
537 interpreter_params,
538 );
539
540 self.execute_tx_inner(&mut transactor, tx)
541 .expect("Expected vm execution to be successful");
542 }
543
544 fn execute_tx_inner<M, Tx, Ecal>(
545 &mut self,
546 transactor: &mut Transactor<M, MemoryStorage, Tx, Ecal>,
547 checked: Checked<Tx>,
548 ) -> anyhow::Result<StateTransition<Tx>>
549 where
550 M: Memory,
551 Tx: ExecutableTransaction,
552 <Tx as IntoChecked>::Metadata: CheckedMetadata,
553 Ecal: crate::interpreter::EcalHandler,
554 {
555 self.storage.set_block_height(self.block_height);
556
557 transactor.transact(checked);
558
559 let storage = transactor.as_mut().clone();
560
561 if let Some(e) = transactor.error() {
562 return Err(anyhow!("{:?}", e));
563 }
564 let is_reverted = transactor.is_reverted();
565
566 let state = transactor.to_owned_state_transition().unwrap();
567
568 let interpreter = transactor.interpreter();
569
570 let transaction: Transaction = interpreter.transaction().clone().into();
572 let tx_offset = self.get_tx_params().tx_offset();
573 let mut tx_mem = interpreter
574 .memory()
575 .read(tx_offset, transaction.size())
576 .unwrap();
577 let mut deser_tx = Transaction::decode(&mut tx_mem).unwrap();
578
579 if let Transaction::Script(ref mut s) = deser_tx {
581 *s.receipts_root_mut() = interpreter.compute_receipts_root();
582 }
583
584 assert_eq!(deser_tx, transaction);
585 if is_reverted {
586 return Ok(state);
587 }
588
589 self.storage = storage;
591
592 Ok(state)
593 }
594
595 pub fn deploy(
596 &mut self,
597 checked: Checked<Create>,
598 ) -> anyhow::Result<StateTransition<Create>> {
599 let interpreter_params =
600 InterpreterParams::new(self.gas_price, &self.consensus_params);
601 let mut transactor = Transactor::<_, _, _>::new(
602 MemoryInstance::new(),
603 self.storage.clone(),
604 interpreter_params,
605 );
606
607 self.execute_tx_inner(&mut transactor, checked)
608 }
609
610 pub fn execute_tx(
611 &mut self,
612 checked: Checked<Script>,
613 ) -> anyhow::Result<StateTransition<Script>> {
614 let interpreter_params =
615 InterpreterParams::new(self.gas_price, &self.consensus_params);
616 let mut transactor = Transactor::<_, _, _>::new(
617 MemoryInstance::new(),
618 self.storage.clone(),
619 interpreter_params,
620 );
621
622 self.execute_tx_inner(&mut transactor, checked)
623 }
624
625 pub fn execute_tx_with_backtrace(
626 &mut self,
627 checked: Checked<Script>,
628 gas_price: u64,
629 ) -> anyhow::Result<(StateTransition<Script>, Option<Backtrace>)> {
630 let interpreter_params =
631 InterpreterParams::new(gas_price, &self.consensus_params);
632 let mut transactor = Transactor::<_, _, _>::new(
633 MemoryInstance::new(),
634 self.storage.clone(),
635 interpreter_params,
636 );
637
638 let state = self.execute_tx_inner(&mut transactor, checked)?;
639 let backtrace = transactor.backtrace();
640
641 Ok((state, backtrace))
642 }
643
644 pub fn execute(&mut self) -> StateTransition<Script> {
646 let tx = self.build();
647
648 self.execute_tx(tx)
649 .expect("expected successful vm execution")
650 }
651
652 pub fn get_storage(&self) -> &MemoryStorage {
653 &self.storage
654 }
655
656 pub fn execute_get_outputs(&mut self) -> Vec<Output> {
657 self.execute().tx().outputs().to_vec()
658 }
659
660 pub fn execute_get_change(&mut self, find_asset_id: AssetId) -> Word {
661 let outputs = self.execute_get_outputs();
662 find_change(outputs, find_asset_id)
663 }
664
665 pub fn get_contract_balance(
666 &mut self,
667 contract_id: &ContractId,
668 asset_id: &AssetId,
669 ) -> Word {
670 let tx = TestBuilder::build_get_balance_tx(
671 contract_id,
672 asset_id,
673 self.consensus_params.tx_params().tx_offset(),
674 );
675 let state = self
676 .execute_tx(tx)
677 .expect("expected successful vm execution in this context");
678 let receipts = state.receipts();
679 receipts[0].ra().expect("Balance expected")
680 }
681 }
682
683 pub fn check_expected_reason_for_instructions(
684 instructions: Vec<Instruction>,
685 expected_reason: PanicReason,
686 ) {
687 let client = MemoryClient::default();
688
689 check_expected_reason_for_instructions_with_client(
690 client,
691 instructions,
692 expected_reason,
693 );
694 }
695
696 pub fn check_expected_reason_for_instructions_with_client<M>(
697 mut client: MemoryClient<M>,
698 instructions: Vec<Instruction>,
699 expected_reason: PanicReason,
700 ) where
701 M: Memory,
702 {
703 let tx_params = TxParameters::default().with_max_gas_per_tx(Word::MAX / 2);
704 let gas_limit = tx_params.max_gas_per_tx() / 2;
707 let maturity = Default::default();
708 let height = Default::default();
709 let zero_fee_limit = 0;
710
711 let contract: Witness = instructions.into_iter().collect::<Vec<u8>>().into();
713 let salt = Default::default();
714 let code_root = Contract::root_from_code(contract.as_ref());
715 let storage_slots = vec![];
716 let state_root = Contract::initial_state_root(storage_slots.iter());
717 let contract_id =
718 Contract::from(contract.as_ref()).id(&salt, &code_root, &state_root);
719
720 let contract_deployer = TransactionBuilder::create(contract, salt, storage_slots)
721 .max_fee_limit(zero_fee_limit)
722 .with_tx_params(tx_params)
723 .add_fee_input()
724 .add_contract_created()
725 .finalize_checked(height);
726
727 client
728 .deploy(contract_deployer)
729 .expect("valid contract deployment");
730
731 let script = [
733 op::gtf(0x10, 0x0, Immediate12::from(GTFArgs::ScriptData)),
735 op::call(0x10, RegId::ZERO, RegId::ZERO, RegId::CGAS),
737 op::ret(RegId::ONE),
738 ]
739 .into_iter()
740 .collect();
741 let script_data: Vec<u8> = [Call::new(contract_id, 0, 0).to_bytes().as_slice()]
742 .into_iter()
743 .flatten()
744 .copied()
745 .collect();
746
747 let tx_deploy_loader = TransactionBuilder::script(script, script_data)
748 .max_fee_limit(zero_fee_limit)
749 .script_gas_limit(gas_limit)
750 .maturity(maturity)
751 .with_tx_params(tx_params)
752 .add_input(Input::contract(
753 Default::default(),
754 Default::default(),
755 Default::default(),
756 Default::default(),
757 contract_id,
758 ))
759 .add_fee_input()
760 .add_output(Output::contract(0, Default::default(), Default::default()))
761 .finalize_checked(height);
762
763 check_reason_for_transaction(client, tx_deploy_loader, expected_reason);
764 }
765
766 pub fn check_reason_for_transaction<M>(
767 mut client: MemoryClient<M>,
768 checked_tx: Checked<Script>,
769 expected_reason: PanicReason,
770 ) where
771 M: Memory,
772 {
773 let receipts = client.transact(checked_tx);
774
775 let panic_found = receipts.iter().any(|receipt| {
776 if let Receipt::Panic { id: _, reason, .. } = receipt {
777 assert_eq!(
778 &expected_reason,
779 reason.reason(),
780 "Expected {}, found {}",
781 expected_reason,
782 reason.reason()
783 );
784 true
785 } else {
786 false
787 }
788 });
789
790 if !panic_found {
791 panic!("Script should have panicked");
792 }
793 }
794
795 pub fn find_change(outputs: Vec<Output>, find_asset_id: AssetId) -> Word {
796 let change = outputs.into_iter().find_map(|output| {
797 if let Output::Change {
798 amount, asset_id, ..
799 } = output
800 {
801 if asset_id == find_asset_id {
802 Some(amount)
803 } else {
804 None
805 }
806 } else {
807 None
808 }
809 });
810 change.unwrap_or_else(|| {
811 panic!("no change matching asset ID {:x} was found", &find_asset_id)
812 })
813 }
814}
815
816#[allow(missing_docs)]
817#[cfg(all(
818 feature = "profile-gas",
819 feature = "std",
820 any(test, feature = "test-helpers")
821))]
822pub mod gas_profiling {
824 use crate::prelude::*;
825
826 use std::sync::{
827 Arc,
828 Mutex,
829 };
830
831 #[derive(Clone)]
832 pub struct GasProfiler {
833 data: Arc<Mutex<Option<ProfilingData>>>,
834 }
835
836 impl Default for GasProfiler {
837 fn default() -> Self {
838 Self {
839 data: Arc::new(Mutex::new(None)),
840 }
841 }
842 }
843
844 impl ProfileReceiver for GasProfiler {
845 fn on_transaction(
846 &mut self,
847 _state: Result<&ProgramState, InterpreterError<String>>,
848 data: &ProfilingData,
849 ) {
850 let mut guard = self.data.lock().unwrap();
851 *guard = Some(data.clone());
852 }
853 }
854
855 impl GasProfiler {
856 pub fn data(&self) -> Option<ProfilingData> {
857 self.data.lock().ok().and_then(|g| g.as_ref().cloned())
858 }
859
860 pub fn total_gas(&self) -> Word {
861 self.data()
862 .map(|d| {
863 d.gas()
864 .iter()
865 .map(|(_, gas)| gas)
866 .copied()
867 .reduce(Word::saturating_add)
868 .unwrap_or_default()
869 })
870 .unwrap_or_default()
871 }
872 }
873}