Skip to main content

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