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