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