linera_sdk/contract/
test_runtime.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Runtime types to simulate interfacing with the host executing the contract.
5
6use std::{
7    collections::{BTreeMap, HashMap, VecDeque},
8    sync::{Arc, Mutex, MutexGuard},
9};
10
11use linera_base::{
12    abi::{ContractAbi, ServiceAbi},
13    data_types::{
14        Amount, ApplicationDescription, ApplicationPermissions, BlockHeight, Bytecode, Resources,
15        SendMessageRequest, Timestamp,
16    },
17    ensure, http,
18    identifiers::{
19        Account, AccountOwner, ApplicationId, BlobId, ChainId, DataBlobHash, ModuleId, StreamName,
20    },
21    ownership::{AccountPermissionError, ChainOwnership, ManageChainError},
22    vm::VmRuntime,
23};
24use serde::Serialize;
25
26use crate::{Contract, KeyValueStore, ViewStorageContext};
27
28struct ExpectedPublishModuleCall {
29    contract: Bytecode,
30    service: Bytecode,
31    vm_runtime: VmRuntime,
32    module_id: ModuleId,
33}
34
35struct ExpectedCreateApplicationCall {
36    module_id: ModuleId,
37    parameters: Vec<u8>,
38    argument: Vec<u8>,
39    required_application_ids: Vec<ApplicationId>,
40    application_id: ApplicationId,
41}
42
43struct ExpectedCreateDataBlobCall {
44    bytes: Vec<u8>,
45    blob_id: BlobId,
46}
47
48/// A mock of the common runtime to interface with the host executing the contract.
49pub struct MockContractRuntime<Application>
50where
51    Application: Contract,
52{
53    application_parameters: Option<Application::Parameters>,
54    application_id: Option<ApplicationId<Application::Abi>>,
55    application_creator_chain_id: Option<ChainId>,
56    application_descriptions: HashMap<ApplicationId, ApplicationDescription>,
57    chain_id: Option<ChainId>,
58    authenticated_owner: Option<Option<AccountOwner>>,
59    block_height: Option<BlockHeight>,
60    round: Option<u32>,
61    message_is_bouncing: Option<Option<bool>>,
62    message_origin_chain_id: Option<Option<ChainId>>,
63    authenticated_caller_id: Option<Option<ApplicationId>>,
64    timestamp: Option<Timestamp>,
65    chain_balance: Option<Amount>,
66    owner_balances: Option<HashMap<AccountOwner, Amount>>,
67    chain_ownership: Option<ChainOwnership>,
68    application_permissions: Option<ApplicationPermissions>,
69    can_manage_chain: Option<bool>,
70    call_application_handler: Option<CallApplicationHandler>,
71    send_message_requests: Arc<Mutex<Vec<SendMessageRequest<Application::Message>>>>,
72    outgoing_transfers: HashMap<Account, Amount>,
73    created_events: BTreeMap<StreamName, Vec<Vec<u8>>>,
74    events: BTreeMap<(ChainId, StreamName, u32), Vec<u8>>,
75    claim_requests: Vec<ClaimRequest>,
76    expected_service_queries: VecDeque<(ApplicationId, String, String)>,
77    expected_http_requests: VecDeque<(http::Request, http::Response)>,
78    expected_read_data_blob_requests: VecDeque<(DataBlobHash, Vec<u8>)>,
79    expected_assert_data_blob_exists_requests: VecDeque<(DataBlobHash, Option<()>)>,
80    expected_has_empty_storage_requests: VecDeque<(ApplicationId, bool)>,
81    expected_open_chain_calls: VecDeque<(ChainOwnership, ApplicationPermissions, Amount, ChainId)>,
82    expected_publish_module_calls: VecDeque<ExpectedPublishModuleCall>,
83    expected_create_application_calls: VecDeque<ExpectedCreateApplicationCall>,
84    expected_create_data_blob_calls: VecDeque<ExpectedCreateDataBlobCall>,
85    key_value_store: KeyValueStore,
86}
87
88impl<Application> Default for MockContractRuntime<Application>
89where
90    Application: Contract,
91{
92    fn default() -> Self {
93        MockContractRuntime::new()
94    }
95}
96
97impl<Application> MockContractRuntime<Application>
98where
99    Application: Contract,
100{
101    /// Creates a new [`MockContractRuntime`] instance for a contract.
102    pub fn new() -> Self {
103        MockContractRuntime {
104            application_parameters: None,
105            application_id: None,
106            application_creator_chain_id: None,
107            application_descriptions: HashMap::new(),
108            chain_id: None,
109            authenticated_owner: None,
110            block_height: None,
111            round: None,
112            message_is_bouncing: None,
113            message_origin_chain_id: None,
114            authenticated_caller_id: None,
115            timestamp: None,
116            chain_balance: None,
117            owner_balances: None,
118            chain_ownership: None,
119            application_permissions: None,
120            can_manage_chain: None,
121            call_application_handler: None,
122            send_message_requests: Arc::default(),
123            outgoing_transfers: HashMap::new(),
124            created_events: BTreeMap::new(),
125            events: BTreeMap::new(),
126            claim_requests: Vec::new(),
127            expected_service_queries: VecDeque::new(),
128            expected_http_requests: VecDeque::new(),
129            expected_read_data_blob_requests: VecDeque::new(),
130            expected_assert_data_blob_exists_requests: VecDeque::new(),
131            expected_has_empty_storage_requests: VecDeque::new(),
132            expected_open_chain_calls: VecDeque::new(),
133            expected_publish_module_calls: VecDeque::new(),
134            expected_create_application_calls: VecDeque::new(),
135            expected_create_data_blob_calls: VecDeque::new(),
136            key_value_store: KeyValueStore::mock().to_mut(),
137        }
138    }
139
140    /// Returns the key-value store to interface with storage.
141    pub fn key_value_store(&self) -> KeyValueStore {
142        self.key_value_store.clone()
143    }
144
145    /// Returns a storage context suitable for a root view.
146    pub fn root_view_storage_context(&self) -> ViewStorageContext {
147        ViewStorageContext::new_unchecked(self.key_value_store(), Vec::new(), ())
148    }
149
150    /// Configures the application parameters to return during the test.
151    pub fn with_application_parameters(
152        mut self,
153        application_parameters: Application::Parameters,
154    ) -> Self {
155        self.application_parameters = Some(application_parameters);
156        self
157    }
158
159    /// Configures the application parameters to return during the test.
160    pub fn set_application_parameters(
161        &mut self,
162        application_parameters: Application::Parameters,
163    ) -> &mut Self {
164        self.application_parameters = Some(application_parameters);
165        self
166    }
167
168    /// Returns the application parameters provided when the application was created.
169    pub fn application_parameters(&mut self) -> Application::Parameters {
170        self.application_parameters.clone().expect(
171            "Application parameters have not been mocked, \
172            please call `MockContractRuntime::set_application_parameters` first",
173        )
174    }
175
176    /// Configures the application ID to return during the test.
177    pub fn with_application_id(mut self, application_id: ApplicationId<Application::Abi>) -> Self {
178        self.application_id = Some(application_id);
179        self
180    }
181
182    /// Configures the application ID to return during the test.
183    pub fn set_application_id(
184        &mut self,
185        application_id: ApplicationId<Application::Abi>,
186    ) -> &mut Self {
187        self.application_id = Some(application_id);
188        self
189    }
190
191    /// Returns the ID of the current application.
192    pub fn application_id(&mut self) -> ApplicationId<Application::Abi> {
193        self.application_id.expect(
194            "Application ID has not been mocked, \
195            please call `MockContractRuntime::set_application_id` first",
196        )
197    }
198
199    /// Configures the application creator chain ID to return during the test.
200    pub fn with_application_creator_chain_id(mut self, chain_id: ChainId) -> Self {
201        self.application_creator_chain_id = Some(chain_id);
202        self
203    }
204
205    /// Configures the application creator chain ID to return during the test.
206    pub fn set_application_creator_chain_id(&mut self, chain_id: ChainId) -> &mut Self {
207        self.application_creator_chain_id = Some(chain_id);
208        self
209    }
210
211    /// Returns the chain ID of the current application creator.
212    pub fn application_creator_chain_id(&mut self) -> ChainId {
213        self.application_creator_chain_id.expect(
214            "Application creator chain ID has not been mocked, \
215            please call `MockContractRuntime::set_application_creator_chain_id` first",
216        )
217    }
218
219    /// Configures the application description to return for a specific application during the test.
220    pub fn with_application_description(
221        mut self,
222        application_id: ApplicationId,
223        description: ApplicationDescription,
224    ) -> Self {
225        self.application_descriptions
226            .insert(application_id, description);
227        self
228    }
229
230    /// Configures the application description to return for a specific application during the test.
231    pub fn set_application_description(
232        &mut self,
233        application_id: ApplicationId,
234        description: ApplicationDescription,
235    ) -> &mut Self {
236        self.application_descriptions
237            .insert(application_id, description);
238        self
239    }
240
241    /// Returns the description of the given application.
242    pub fn read_application_description(
243        &mut self,
244        application_id: ApplicationId,
245    ) -> ApplicationDescription {
246        self.application_descriptions
247            .get(&application_id)
248            .cloned()
249            .unwrap_or_else(|| {
250                panic!(
251                    "Application description for {application_id:?} has not been mocked, \
252                    please call `MockContractRuntime::set_application_description` first"
253                )
254            })
255    }
256
257    /// Configures the chain ID to return during the test.
258    pub fn with_chain_id(mut self, chain_id: ChainId) -> Self {
259        self.chain_id = Some(chain_id);
260        self
261    }
262
263    /// Configures the chain ID to return during the test.
264    pub fn set_chain_id(&mut self, chain_id: ChainId) -> &mut Self {
265        self.chain_id = Some(chain_id);
266        self
267    }
268
269    /// Returns the ID of the current chain.
270    pub fn chain_id(&mut self) -> ChainId {
271        self.chain_id.expect(
272            "Chain ID has not been mocked, \
273            please call `MockContractRuntime::set_chain_id` first",
274        )
275    }
276
277    /// Configures the authenticated owner to return during the test.
278    pub fn with_authenticated_owner(
279        mut self,
280        authenticated_owner: impl Into<Option<AccountOwner>>,
281    ) -> Self {
282        self.authenticated_owner = Some(authenticated_owner.into());
283        self
284    }
285
286    /// Configures the authenticated owner to return during the test.
287    pub fn set_authenticated_owner(
288        &mut self,
289        authenticated_owner: impl Into<Option<AccountOwner>>,
290    ) -> &mut Self {
291        self.authenticated_owner = Some(authenticated_owner.into());
292        self
293    }
294
295    /// Returns the authenticated owner for this execution, if there is one.
296    pub fn authenticated_owner(&mut self) -> Option<AccountOwner> {
297        self.authenticated_owner.expect(
298            "Authenticated owner has not been mocked, \
299            please call `MockContractRuntime::set_authenticated_owner` first",
300        )
301    }
302
303    /// Configures the block height to return during the test.
304    pub fn with_block_height(mut self, block_height: BlockHeight) -> Self {
305        self.block_height = Some(block_height);
306        self
307    }
308
309    /// Configures the block height to return during the test.
310    pub fn set_block_height(&mut self, block_height: BlockHeight) -> &mut Self {
311        self.block_height = Some(block_height);
312        self
313    }
314
315    /// Configures the multi-leader round number to return during the test.
316    pub fn with_round(mut self, round: u32) -> Self {
317        self.round = Some(round);
318        self
319    }
320
321    /// Configures the multi-leader round number to return during the test.
322    pub fn set_round(&mut self, round: u32) -> &mut Self {
323        self.round = Some(round);
324        self
325    }
326
327    /// Returns the height of the current block that is executing.
328    pub fn block_height(&mut self) -> BlockHeight {
329        self.block_height.expect(
330            "Block height has not been mocked, \
331            please call `MockContractRuntime::set_block_height` first",
332        )
333    }
334
335    /// Configures the `message_is_bouncing` flag to return during the test.
336    pub fn with_message_is_bouncing(
337        mut self,
338        message_is_bouncing: impl Into<Option<bool>>,
339    ) -> Self {
340        self.message_is_bouncing = Some(message_is_bouncing.into());
341        self
342    }
343
344    /// Configures the `message_is_bouncing` flag to return during the test.
345    pub fn set_message_is_bouncing(
346        &mut self,
347        message_is_bouncing: impl Into<Option<bool>>,
348    ) -> &mut Self {
349        self.message_is_bouncing = Some(message_is_bouncing.into());
350        self
351    }
352
353    /// Returns [`true`] if the incoming message was rejected from the original destination and is
354    /// now bouncing back, or [`None`] if not executing an incoming message.
355    pub fn message_is_bouncing(&mut self) -> Option<bool> {
356        self.message_is_bouncing.expect(
357            "`message_is_bouncing` flag has not been mocked, \
358            please call `MockContractRuntime::set_message_is_bouncing` first",
359        )
360    }
361
362    /// Configures the `message_origin_chain_id` to return during the test.
363    pub fn set_message_origin_chain_id(
364        &mut self,
365        message_origin_chain_id: impl Into<Option<ChainId>>,
366    ) -> &mut Self {
367        self.message_origin_chain_id = Some(message_origin_chain_id.into());
368        self
369    }
370
371    /// Returns the chain ID where the incoming message originated from, or [`None`] if not
372    /// executing an incoming message.
373    pub fn message_origin_chain_id(&mut self) -> Option<ChainId> {
374        self.message_origin_chain_id.expect(
375            "`message_origin_chain_id` has not been mocked, \
376            please call `MockContractRuntime::set_message_origin_chain_id` first",
377        )
378    }
379
380    /// Configures the authenticated caller ID to return during the test.
381    pub fn with_authenticated_caller_id(
382        mut self,
383        authenticated_caller_id: impl Into<Option<ApplicationId>>,
384    ) -> Self {
385        self.authenticated_caller_id = Some(authenticated_caller_id.into());
386        self
387    }
388
389    /// Configures the authenticated caller ID to return during the test.
390    pub fn set_authenticated_caller_id(
391        &mut self,
392        authenticated_caller_id: impl Into<Option<ApplicationId>>,
393    ) -> &mut Self {
394        self.authenticated_caller_id = Some(authenticated_caller_id.into());
395        self
396    }
397
398    /// Returns the authenticated caller ID, if the caller configured it and if the current context
399    /// is executing a cross-application call.
400    pub fn authenticated_caller_id(&mut self) -> Option<ApplicationId> {
401        self.authenticated_caller_id.expect(
402            "Authenticated caller ID has not been mocked, \
403            please call `MockContractRuntime::set_authenticated_caller_id` first",
404        )
405    }
406
407    /// Verifies that the current execution context authorizes operations on a given account.
408    pub fn check_account_permission(
409        &mut self,
410        owner: AccountOwner,
411    ) -> Result<(), AccountPermissionError> {
412        ensure!(
413            self.authenticated_owner() == Some(owner)
414                || self.authenticated_caller_id().map(AccountOwner::from) == Some(owner),
415            AccountPermissionError::NotPermitted(owner)
416        );
417        Ok(())
418    }
419
420    /// Configures the system time to return during the test.
421    pub fn with_system_time(mut self, timestamp: Timestamp) -> Self {
422        self.timestamp = Some(timestamp);
423        self
424    }
425
426    /// Configures the system time to return during the test.
427    pub fn set_system_time(&mut self, timestamp: Timestamp) -> &mut Self {
428        self.timestamp = Some(timestamp);
429        self
430    }
431
432    /// Retrieves the current system time, i.e. the timestamp of the block in which this is called.
433    pub fn system_time(&mut self) -> Timestamp {
434        self.timestamp.expect(
435            "System time has not been mocked, \
436            please call `MockContractRuntime::set_system_time` first",
437        )
438    }
439
440    /// Configures the chain balance to return during the test.
441    pub fn with_chain_balance(mut self, chain_balance: Amount) -> Self {
442        self.chain_balance = Some(chain_balance);
443        self
444    }
445
446    /// Configures the chain balance to return during the test.
447    pub fn set_chain_balance(&mut self, chain_balance: Amount) -> &mut Self {
448        self.chain_balance = Some(chain_balance);
449        self
450    }
451
452    /// Returns the current chain balance.
453    pub fn chain_balance(&mut self) -> Amount {
454        *self.chain_balance_mut()
455    }
456
457    /// Returns a mutable reference to the current chain balance.
458    fn chain_balance_mut(&mut self) -> &mut Amount {
459        self.chain_balance.as_mut().expect(
460            "Chain balance has not been mocked, \
461            please call `MockContractRuntime::set_chain_balance` first",
462        )
463    }
464
465    /// Configures the balances on the chain to use during the test.
466    pub fn with_owner_balances(
467        mut self,
468        owner_balances: impl IntoIterator<Item = (AccountOwner, Amount)>,
469    ) -> Self {
470        self.owner_balances = Some(owner_balances.into_iter().collect());
471        self
472    }
473
474    /// Configures the balances on the chain to use during the test.
475    pub fn set_owner_balances(
476        &mut self,
477        owner_balances: impl IntoIterator<Item = (AccountOwner, Amount)>,
478    ) -> &mut Self {
479        self.owner_balances = Some(owner_balances.into_iter().collect());
480        self
481    }
482
483    /// Configures the balance of one account on the chain to use during the test.
484    pub fn with_owner_balance(mut self, owner: AccountOwner, balance: Amount) -> Self {
485        self.set_owner_balance(owner, balance);
486        self
487    }
488
489    /// Configures the balance of one account on the chain to use during the test.
490    pub fn set_owner_balance(&mut self, owner: AccountOwner, balance: Amount) -> &mut Self {
491        self.owner_balances
492            .get_or_insert_with(HashMap::new)
493            .insert(owner, balance);
494        self
495    }
496
497    /// Returns the balance of one of the accounts on this chain.
498    pub fn owner_balance(&mut self, owner: AccountOwner) -> Amount {
499        *self.owner_balance_mut(owner)
500    }
501
502    /// Returns a mutable reference to the balance of one of the accounts on this chain.
503    fn owner_balance_mut(&mut self, owner: AccountOwner) -> &mut Amount {
504        self.owner_balances
505            .as_mut()
506            .expect(
507                "Owner balances have not been mocked, \
508                please call `MockContractRuntime::set_owner_balances` first",
509            )
510            .get_mut(&owner)
511            .unwrap_or_else(|| {
512                panic!(
513                    "Balance for owner {owner} was not mocked, \
514                    please include a balance for them in the call to \
515                    `MockContractRuntime::set_owner_balances`"
516                )
517            })
518    }
519
520    /// Schedules a message to be sent to this application on another chain.
521    pub fn send_message(&mut self, destination: ChainId, message: Application::Message) {
522        self.prepare_message(message).send_to(destination)
523    }
524
525    /// Returns a `MessageBuilder` to prepare a message to be sent.
526    pub fn prepare_message(
527        &mut self,
528        message: Application::Message,
529    ) -> MessageBuilder<Application::Message> {
530        MessageBuilder::new(message, self.send_message_requests.clone())
531    }
532
533    /// Returns the list of [`SendMessageRequest`]s created so far during the test.
534    pub fn created_send_message_requests(
535        &self,
536    ) -> MutexGuard<'_, Vec<SendMessageRequest<Application::Message>>> {
537        self.send_message_requests
538            .try_lock()
539            .expect("Unit test should be single-threaded")
540    }
541
542    /// Transfers an `amount` of native tokens from `source` owner account (or the current chain's
543    /// balance) to `destination`.
544    pub fn transfer(&mut self, source: AccountOwner, destination: Account, amount: Amount) {
545        self.debit(source, amount);
546
547        if Some(destination.chain_id) == self.chain_id {
548            self.credit(destination.owner, amount);
549        } else {
550            let destination_entry = self.outgoing_transfers.entry(destination).or_default();
551            *destination_entry = destination_entry
552                .try_add(amount)
553                .expect("Outgoing transfer value overflow");
554        }
555    }
556
557    /// Debits an `amount` of native tokens from a `source` owner account (or the current
558    /// chain's balance).
559    fn debit(&mut self, source: AccountOwner, amount: Amount) {
560        let source_balance = if source == AccountOwner::CHAIN {
561            self.chain_balance_mut()
562        } else {
563            self.owner_balance_mut(source)
564        };
565
566        *source_balance = source_balance
567            .try_sub(amount)
568            .expect("Insufficient funds in source account");
569    }
570
571    /// Credits an `amount` of native tokens into a `destination` owner account (or the
572    /// current chain's balance).
573    fn credit(&mut self, destination: AccountOwner, amount: Amount) {
574        let destination_balance = if destination == AccountOwner::CHAIN {
575            self.chain_balance_mut()
576        } else {
577            self.owner_balance_mut(destination)
578        };
579
580        *destination_balance = destination_balance
581            .try_add(amount)
582            .expect("Account balance overflow");
583    }
584
585    /// Returns the outgoing transfers scheduled during the test so far.
586    pub fn outgoing_transfers(&self) -> &HashMap<Account, Amount> {
587        &self.outgoing_transfers
588    }
589
590    /// Claims an `amount` of native tokens from a `source` account to a `destination` account.
591    pub fn claim(&mut self, source: Account, destination: Account, amount: Amount) {
592        if Some(source.chain_id) == self.chain_id {
593            self.debit(source.owner, amount);
594
595            if Some(destination.chain_id) == self.chain_id {
596                self.credit(destination.owner, amount);
597            }
598        }
599
600        self.claim_requests.push(ClaimRequest {
601            source,
602            amount,
603            destination,
604        });
605    }
606
607    /// Returns the list of claims made during the test so far.
608    pub fn claim_requests(&self) -> &[ClaimRequest] {
609        &self.claim_requests
610    }
611
612    /// Configures the chain ownership configuration to return during the test.
613    pub fn with_chain_ownership(mut self, chain_ownership: ChainOwnership) -> Self {
614        self.chain_ownership = Some(chain_ownership);
615        self
616    }
617
618    /// Configures the chain ownership configuration to return during the test.
619    pub fn set_chain_ownership(&mut self, chain_ownership: ChainOwnership) -> &mut Self {
620        self.chain_ownership = Some(chain_ownership);
621        self
622    }
623
624    /// Retrieves the owner configuration for the current chain.
625    pub fn chain_ownership(&mut self) -> ChainOwnership {
626        self.chain_ownership.clone().expect(
627            "Chain ownership has not been mocked, \
628            please call `MockContractRuntime::set_chain_ownership` first",
629        )
630    }
631
632    /// Configures the application permissions to return during the test.
633    pub fn with_application_permissions(
634        mut self,
635        application_permissions: ApplicationPermissions,
636    ) -> Self {
637        self.application_permissions = Some(application_permissions);
638        self
639    }
640
641    /// Configures the application permissions to return during the test.
642    pub fn set_application_permissions(
643        &mut self,
644        application_permissions: ApplicationPermissions,
645    ) -> &mut Self {
646        self.application_permissions = Some(application_permissions);
647        self
648    }
649
650    /// Retrieves the application permissions for the current chain.
651    pub fn application_permissions(&mut self) -> ApplicationPermissions {
652        self.application_permissions.clone().expect(
653            "Application permissions have not been mocked, \
654            please call `MockContractRuntime::set_application_permissions` first",
655        )
656    }
657
658    /// Configures if the application being tested is allowed to manage the chain, i.e. close
659    /// it, change the application permissions, and change the ownership.
660    pub fn with_can_manage_chain(mut self, can_manage_chain: bool) -> Self {
661        self.can_manage_chain = Some(can_manage_chain);
662        self
663    }
664
665    /// Configures if the application being tested is allowed to manage the chain, i.e. close
666    /// it, change the application permissions, and change the ownership.
667    pub fn set_can_manage_chain(&mut self, can_manage_chain: bool) -> &mut Self {
668        self.can_manage_chain = Some(can_manage_chain);
669        self
670    }
671
672    /// Closes the current chain. Returns an error if the application doesn't have
673    /// permission to do so.
674    pub fn close_chain(&mut self) -> Result<(), ManageChainError> {
675        let authorized = self.can_manage_chain.expect(
676            "Authorization to manage the chain has not been mocked, \
677            please call `MockContractRuntime::set_can_manage_chain` first",
678        );
679
680        if authorized {
681            Ok(())
682        } else {
683            Err(ManageChainError::NotPermitted)
684        }
685    }
686
687    /// Changes the ownership of the current chain. Returns an error if the application doesn't
688    /// have permission to do so.
689    pub fn change_ownership(&mut self, ownership: ChainOwnership) -> Result<(), ManageChainError> {
690        let authorized = self.can_manage_chain.expect(
691            "Authorization to manage the chain has not been mocked, \
692            please call `MockContractRuntime::set_can_manage_chain` first",
693        );
694
695        if authorized {
696            self.chain_ownership = Some(ownership);
697            Ok(())
698        } else {
699            Err(ManageChainError::NotPermitted)
700        }
701    }
702
703    /// Changes the application permissions on the current chain. Returns an error if the
704    /// application doesn't have permission to do so.
705    pub fn change_application_permissions(
706        &mut self,
707        application_permissions: ApplicationPermissions,
708    ) -> Result<(), ManageChainError> {
709        let authorized = self.can_manage_chain.expect(
710            "Authorization to manage the chain has not been mocked, \
711            please call `MockContractRuntime::set_can_manage_chain` first",
712        );
713
714        if authorized {
715            let application_id = self
716                .application_id
717                .expect("The application doesn't have an ID!")
718                .forget_abi();
719            self.can_manage_chain = Some(application_permissions.can_manage_chain(&application_id));
720            Ok(())
721        } else {
722            Err(ManageChainError::NotPermitted)
723        }
724    }
725
726    /// Adds an expected call to `open_chain`, and the child chain ID that should be returned.
727    pub fn add_expected_open_chain_call(
728        &mut self,
729        ownership: ChainOwnership,
730        application_permissions: ApplicationPermissions,
731        balance: Amount,
732        chain_id: ChainId,
733    ) {
734        self.expected_open_chain_calls.push_back((
735            ownership,
736            application_permissions,
737            balance,
738            chain_id,
739        ));
740    }
741
742    /// Opens a new chain, configuring it with the provided `chain_ownership`,
743    /// `application_permissions` and initial `balance` (debited from the current chain).
744    pub fn open_chain(
745        &mut self,
746        ownership: ChainOwnership,
747        application_permissions: ApplicationPermissions,
748        balance: Amount,
749    ) -> ChainId {
750        let (expected_ownership, expected_permissions, expected_balance, chain_id) = self
751            .expected_open_chain_calls
752            .pop_front()
753            .expect("Unexpected open_chain call");
754        assert_eq!(ownership, expected_ownership);
755        assert_eq!(application_permissions, expected_permissions);
756        assert_eq!(balance, expected_balance);
757        chain_id
758    }
759
760    /// Adds a new expected call to `publish_module`.
761    pub fn add_expected_publish_module_call(
762        &mut self,
763        contract: Bytecode,
764        service: Bytecode,
765        vm_runtime: VmRuntime,
766        module_id: ModuleId,
767    ) {
768        self.expected_publish_module_calls
769            .push_back(ExpectedPublishModuleCall {
770                contract,
771                service,
772                vm_runtime,
773                module_id,
774            });
775    }
776
777    /// Adds a new expected call to `create_application`.
778    pub fn add_expected_create_application_call<Parameters, InstantiationArgument>(
779        &mut self,
780        module_id: ModuleId,
781        parameters: &Parameters,
782        argument: &InstantiationArgument,
783        required_application_ids: Vec<ApplicationId>,
784        application_id: ApplicationId,
785    ) where
786        Parameters: Serialize,
787        InstantiationArgument: Serialize,
788    {
789        let parameters = serde_json::to_vec(parameters)
790            .expect("Failed to serialize `Parameters` type for a cross-application call");
791        let argument = serde_json::to_vec(argument).expect(
792            "Failed to serialize `InstantiationArgument` type for a cross-application call",
793        );
794        self.expected_create_application_calls
795            .push_back(ExpectedCreateApplicationCall {
796                module_id,
797                parameters,
798                argument,
799                required_application_ids,
800                application_id,
801            });
802    }
803
804    /// Adds a new expected call to `create_data_blob`.
805    pub fn add_expected_create_data_blob_call(&mut self, bytes: Vec<u8>, blob_id: BlobId) {
806        self.expected_create_data_blob_calls
807            .push_back(ExpectedCreateDataBlobCall { bytes, blob_id });
808    }
809
810    /// Creates a new module-id on-chain application, based on the supplied bytecode and parameters.
811    pub fn publish_module(
812        &mut self,
813        contract: Bytecode,
814        service: Bytecode,
815        vm_runtime: VmRuntime,
816    ) -> ModuleId {
817        let ExpectedPublishModuleCall {
818            contract: expected_contract,
819            service: expected_service,
820            vm_runtime: expected_vm_runtime,
821            module_id,
822        } = self
823            .expected_publish_module_calls
824            .pop_front()
825            .expect("Unexpected publish_module call");
826        assert_eq!(contract, expected_contract);
827        assert_eq!(service, expected_service);
828        assert_eq!(vm_runtime, expected_vm_runtime);
829        module_id
830    }
831
832    /// Creates a new on-chain application, based on the supplied module and parameters.
833    pub fn create_application<Abi, Parameters, InstantiationArgument>(
834        &mut self,
835        module_id: ModuleId,
836        parameters: &Parameters,
837        argument: &InstantiationArgument,
838        required_application_ids: Vec<ApplicationId>,
839    ) -> ApplicationId<Abi>
840    where
841        Abi: ContractAbi,
842        Parameters: Serialize,
843        InstantiationArgument: Serialize,
844    {
845        let ExpectedCreateApplicationCall {
846            module_id: expected_module_id,
847            parameters: expected_parameters,
848            argument: expected_argument,
849            required_application_ids: expected_required_app_ids,
850            application_id,
851        } = self
852            .expected_create_application_calls
853            .pop_front()
854            .expect("Unexpected create_application call");
855        let parameters = serde_json::to_vec(parameters)
856            .expect("Failed to serialize `Parameters` type for a cross-application call");
857        let argument = serde_json::to_vec(argument).expect(
858            "Failed to serialize `InstantiationArgument` type for a cross-application call",
859        );
860        assert_eq!(module_id, expected_module_id);
861        assert_eq!(parameters, expected_parameters);
862        assert_eq!(argument, expected_argument);
863        assert_eq!(required_application_ids, expected_required_app_ids);
864        application_id.with_abi::<Abi>()
865    }
866
867    /// Creates a new data blob and returns its hash.
868    pub fn create_data_blob(&mut self, bytes: &[u8]) -> DataBlobHash {
869        let ExpectedCreateDataBlobCall {
870            bytes: expected_bytes,
871            blob_id,
872        } = self
873            .expected_create_data_blob_calls
874            .pop_front()
875            .expect("Unexpected create_data_blob call");
876        assert_eq!(bytes, &expected_bytes);
877        DataBlobHash(blob_id.hash)
878    }
879
880    /// Configures the handler for cross-application calls made during the test.
881    pub fn with_call_application_handler(
882        mut self,
883        handler: impl FnMut(bool, ApplicationId, Vec<u8>) -> Vec<u8> + 'static,
884    ) -> Self {
885        self.call_application_handler = Some(Box::new(handler));
886        self
887    }
888
889    /// Configures the handler for cross-application calls made during the test.
890    pub fn set_call_application_handler(
891        &mut self,
892        handler: impl FnMut(bool, ApplicationId, Vec<u8>) -> Vec<u8> + 'static,
893    ) -> &mut Self {
894        self.call_application_handler = Some(Box::new(handler));
895        self
896    }
897
898    /// Calls another application.
899    pub fn call_application<A: ContractAbi + Send>(
900        &mut self,
901        authenticated: bool,
902        application: ApplicationId<A>,
903        call: &A::Operation,
904    ) -> A::Response {
905        let call_bytes = <A as ContractAbi>::serialize_operation(call)
906            .expect("Failed to serialize `Operation` in test runtime cross-application call");
907
908        let handler = self.call_application_handler.as_mut().expect(
909            "Handler for `call_application` has not been mocked, \
910            please call `MockContractRuntime::set_call_application_handler` first",
911        );
912        let response_bytes = handler(authenticated, application.forget_abi(), call_bytes);
913
914        A::deserialize_response(response_bytes)
915            .expect("Failed to deserialize `Response` in test runtime cross-application call")
916    }
917
918    /// Adds a new item to an event stream. Returns the new event's index in the stream.
919    pub fn emit(&mut self, name: StreamName, value: &Application::EventValue) -> u32 {
920        let value = bcs::to_bytes(value).expect("Failed to serialize event value");
921        let entry = self.created_events.entry(name).or_default();
922        entry.push(value);
923        entry.len() as u32 - 1
924    }
925
926    /// Adds an event to a stream, so that it can be read using `read_event`.
927    pub fn add_event(&mut self, chain_id: ChainId, name: StreamName, index: u32, value: &[u8]) {
928        self.events.insert((chain_id, name, index), value.to_vec());
929    }
930
931    /// Reads an event from a stream. Returns the event's value.
932    ///
933    /// Panics if the event doesn't exist.
934    pub fn read_event(
935        &mut self,
936        chain_id: ChainId,
937        name: StreamName,
938        index: u32,
939    ) -> Application::EventValue {
940        let value = self
941            .events
942            .get(&(chain_id, name, index))
943            .expect("Event not found");
944        bcs::from_bytes(value).expect("Failed to deserialize event value")
945    }
946
947    /// Subscribes this application to an event stream.
948    pub fn subscribe_to_events(
949        &mut self,
950        _chain_id: ChainId,
951        _application_id: ApplicationId,
952        _name: StreamName,
953    ) {
954        // This is a no-op in the mock runtime.
955    }
956
957    /// Unsubscribes this application from an event stream.
958    pub fn unsubscribe_from_events(
959        &mut self,
960        _chain_id: ChainId,
961        _application_id: ApplicationId,
962        _name: StreamName,
963    ) {
964        // This is a no-op in the mock runtime.
965    }
966
967    /// Adds an expected `query_service` call`, and the response it should return in the test.
968    pub fn add_expected_service_query<A: ServiceAbi + Send>(
969        &mut self,
970        application_id: ApplicationId<A>,
971        query: A::Query,
972        response: A::QueryResponse,
973    ) {
974        let query = serde_json::to_string(&query).expect("Failed to serialize query");
975        let response = serde_json::to_string(&response).expect("Failed to serialize response");
976        self.expected_service_queries
977            .push_back((application_id.forget_abi(), query, response));
978    }
979
980    /// Adds an expected `http_request` call, and the response it should return in the test.
981    pub fn add_expected_http_request(&mut self, request: http::Request, response: http::Response) {
982        self.expected_http_requests.push_back((request, response));
983    }
984
985    /// Adds an expected `read_data_blob` call, and the response it should return in the test.
986    pub fn add_expected_read_data_blob_requests(&mut self, hash: DataBlobHash, response: Vec<u8>) {
987        self.expected_read_data_blob_requests
988            .push_back((hash, response));
989    }
990
991    /// Adds an expected `assert_data_blob_exists` call, and the response it should return in the test.
992    pub fn add_expected_assert_data_blob_exists_requests(
993        &mut self,
994        hash: DataBlobHash,
995        response: Option<()>,
996    ) {
997        self.expected_assert_data_blob_exists_requests
998            .push_back((hash, response));
999    }
1000
1001    /// Adds an expected `has_empty_storage` call, and the response it should return in the test.
1002    pub fn add_expected_has_empty_storage_requests(
1003        &mut self,
1004        application: ApplicationId,
1005        response: bool,
1006    ) {
1007        self.expected_has_empty_storage_requests
1008            .push_back((application, response));
1009    }
1010
1011    /// Queries an application service as an oracle and returns the response.
1012    ///
1013    /// Should only be used with queries where it is very likely that all validators will compute
1014    /// the same result, otherwise most block proposals will fail.
1015    ///
1016    /// Cannot be used in fast blocks: A block using this call should be proposed by a regular
1017    /// owner, not a super owner.
1018    pub fn query_service<A: ServiceAbi + Send>(
1019        &mut self,
1020        application_id: ApplicationId<A>,
1021        query: &A::Query,
1022    ) -> A::QueryResponse {
1023        let maybe_query = self.expected_service_queries.pop_front();
1024        let (expected_id, expected_query, response) =
1025            maybe_query.expect("Unexpected service query");
1026        assert_eq!(application_id.forget_abi(), expected_id);
1027        let query = serde_json::to_string(query).expect("Failed to serialize query");
1028        assert_eq!(query, expected_query);
1029        serde_json::from_str(&response).expect("Failed to deserialize response")
1030    }
1031
1032    /// Makes an HTTP `request` as an oracle and returns the HTTP response.
1033    ///
1034    /// Should only be used with queries where it is very likely that all validators will receive
1035    /// the same response, otherwise most block proposals will fail.
1036    ///
1037    /// Cannot be used in fast blocks: A block using this call should be proposed by a regular
1038    /// owner, not a super owner.
1039    pub fn http_request(&mut self, request: http::Request) -> http::Response {
1040        let maybe_request = self.expected_http_requests.pop_front();
1041        let (expected_request, response) = maybe_request.expect("Unexpected HTTP request");
1042        assert_eq!(request, expected_request);
1043        response
1044    }
1045
1046    /// Panics if the current time at block validation is `>= timestamp`. Note that block
1047    /// validation happens at or after the block timestamp, but isn't necessarily the same.
1048    ///
1049    /// Cannot be used in fast blocks: A block using this call should be proposed by a regular
1050    /// owner, not a super owner.
1051    pub fn assert_before(&mut self, timestamp: Timestamp) {
1052        assert!(self.timestamp.is_some_and(|t| t < timestamp))
1053    }
1054
1055    /// Reads a data blob with the given hash from storage.
1056    pub fn read_data_blob(&mut self, hash: DataBlobHash) -> Vec<u8> {
1057        let maybe_request = self.expected_read_data_blob_requests.pop_front();
1058        let (expected_hash, response) = maybe_request.expect("Unexpected read_data_blob request");
1059        assert_eq!(hash, expected_hash);
1060        response
1061    }
1062
1063    /// Asserts that a blob with the given hash exists in storage.
1064    pub fn assert_data_blob_exists(&mut self, hash: DataBlobHash) {
1065        let maybe_request = self.expected_assert_data_blob_exists_requests.pop_front();
1066        let (expected_blob_hash, response) =
1067            maybe_request.expect("Unexpected assert_data_blob_exists request");
1068        assert_eq!(hash, expected_blob_hash);
1069        response.expect("Blob does not exist!");
1070    }
1071
1072    /// Returns true if the corresponding contract uses a zero amount of storage.
1073    pub fn has_empty_storage(&mut self, application: ApplicationId) -> bool {
1074        let maybe_request = self.expected_has_empty_storage_requests.pop_front();
1075        let (expected_application_id, response) =
1076            maybe_request.expect("Unexpected has_empty_storage request");
1077        assert_eq!(application, expected_application_id);
1078        response
1079    }
1080
1081    /// Returns the multi-leader round in which this block was validated.
1082    pub fn validation_round(&mut self) -> Option<u32> {
1083        self.round
1084    }
1085}
1086
1087/// A type alias for the handler for cross-application calls.
1088pub type CallApplicationHandler = Box<dyn FnMut(bool, ApplicationId, Vec<u8>) -> Vec<u8>>;
1089
1090/// A helper type that uses the builder pattern to configure how a message is sent, and then
1091/// sends the message once it is dropped.
1092#[must_use]
1093pub struct MessageBuilder<Message>
1094where
1095    Message: Serialize,
1096{
1097    authenticated: bool,
1098    is_tracked: bool,
1099    grant: Resources,
1100    message: Message,
1101    send_message_requests: Arc<Mutex<Vec<SendMessageRequest<Message>>>>,
1102}
1103
1104impl<Message> MessageBuilder<Message>
1105where
1106    Message: Serialize,
1107{
1108    /// Creates a new [`MessageBuilder`] instance to send the `message` to the `destination`.
1109    pub(crate) fn new(
1110        message: Message,
1111        send_message_requests: Arc<Mutex<Vec<SendMessageRequest<Message>>>>,
1112    ) -> Self {
1113        MessageBuilder {
1114            authenticated: false,
1115            is_tracked: false,
1116            grant: Resources::default(),
1117            message,
1118            send_message_requests,
1119        }
1120    }
1121
1122    /// Marks the message to be tracked, so that the sender receives the message back if it is
1123    /// rejected by the receiver.
1124    pub fn with_tracking(mut self) -> Self {
1125        self.is_tracked = true;
1126        self
1127    }
1128
1129    /// Forwards the authenticated owner with the message.
1130    pub fn with_authentication(mut self) -> Self {
1131        self.authenticated = true;
1132        self
1133    }
1134
1135    /// Forwards a grant of resources so the receiver can use it to pay for receiving the message.
1136    pub fn with_grant(mut self, grant: Resources) -> Self {
1137        self.grant = grant;
1138        self
1139    }
1140
1141    /// Schedules this `Message` to be sent to the `destination`.
1142    pub fn send_to(self, destination: ChainId) {
1143        let request = SendMessageRequest {
1144            destination,
1145            authenticated: self.authenticated,
1146            is_tracked: self.is_tracked,
1147            grant: self.grant,
1148            message: self.message,
1149        };
1150
1151        self.send_message_requests
1152            .try_lock()
1153            .expect("Unit test should be single-threaded")
1154            .push(request);
1155    }
1156}
1157
1158/// A claim request that was scheduled to be sent during this test.
1159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1160pub struct ClaimRequest {
1161    source: Account,
1162    destination: Account,
1163    amount: Amount,
1164}