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