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