near_sdk/
promise.rs

1#[cfg(feature = "abi")]
2use borsh::BorshSchema;
3use std::cell::RefCell;
4#[cfg(any(feature = "abi", feature = "deterministic-account-ids"))]
5use std::collections::BTreeMap;
6use std::collections::VecDeque;
7use std::io::{Error, Write};
8use std::num::NonZeroU128;
9use std::rc::Rc;
10
11use crate::env::migrate_to_allowance;
12#[cfg(any(feature = "deterministic-account-ids", feature = "global-contracts"))]
13use crate::CryptoHash;
14use crate::{AccountId, Gas, GasWeight, NearToken, PromiseIndex, PublicKey};
15
16/// Allow an access key to spend either an unlimited or limited amount of gas
17// This wrapper prevents incorrect construction
18#[derive(Clone, Copy)]
19pub enum Allowance {
20    Unlimited,
21    Limited(NonZeroU128),
22}
23
24impl Allowance {
25    pub fn unlimited() -> Allowance {
26        Allowance::Unlimited
27    }
28
29    /// This will return an None if you try to pass a zero value balance
30    pub fn limited(balance: NearToken) -> Option<Allowance> {
31        NonZeroU128::new(balance.as_yoctonear()).map(Allowance::Limited)
32    }
33}
34
35enum PromiseAction {
36    CreateAccount,
37    DeployContract {
38        code: Vec<u8>,
39    },
40    FunctionCall {
41        function_name: String,
42        arguments: Vec<u8>,
43        amount: NearToken,
44        gas: Gas,
45    },
46    FunctionCallWeight {
47        function_name: String,
48        arguments: Vec<u8>,
49        amount: NearToken,
50        gas: Gas,
51        weight: GasWeight,
52    },
53    Transfer {
54        amount: NearToken,
55    },
56    Stake {
57        amount: NearToken,
58        public_key: PublicKey,
59    },
60    AddFullAccessKey {
61        public_key: PublicKey,
62        nonce: u64,
63    },
64    AddAccessKey {
65        public_key: PublicKey,
66        allowance: Allowance,
67        receiver_id: AccountId,
68        function_names: String,
69        nonce: u64,
70    },
71    DeleteKey {
72        public_key: PublicKey,
73    },
74    DeleteAccount {
75        beneficiary_id: AccountId,
76    },
77    #[cfg(feature = "global-contracts")]
78    DeployGlobalContract {
79        code: Vec<u8>,
80    },
81    #[cfg(feature = "global-contracts")]
82    DeployGlobalContractByAccountId {
83        code: Vec<u8>,
84    },
85    #[cfg(feature = "global-contracts")]
86    UseGlobalContract {
87        code_hash: CryptoHash,
88    },
89    #[cfg(feature = "global-contracts")]
90    UseGlobalContractByAccountId {
91        account_id: AccountId,
92    },
93    #[cfg(feature = "deterministic-account-ids")]
94    DeterministicStateInit {
95        state_init: crate::state_init::StateInit,
96        deposit: NearToken,
97    },
98}
99
100impl PromiseAction {
101    pub fn add(&self, promise_index: PromiseIndex) {
102        use PromiseAction::*;
103        match self {
104            CreateAccount => crate::env::promise_batch_action_create_account(promise_index),
105            DeployContract { code } => {
106                crate::env::promise_batch_action_deploy_contract(promise_index, code)
107            }
108            FunctionCall { function_name, arguments, amount, gas } => {
109                crate::env::promise_batch_action_function_call(
110                    promise_index,
111                    function_name,
112                    arguments,
113                    *amount,
114                    *gas,
115                )
116            }
117            FunctionCallWeight { function_name, arguments, amount, gas, weight } => {
118                crate::env::promise_batch_action_function_call_weight(
119                    promise_index,
120                    function_name,
121                    arguments,
122                    *amount,
123                    *gas,
124                    GasWeight(weight.0),
125                )
126            }
127            Transfer { amount } => {
128                crate::env::promise_batch_action_transfer(promise_index, *amount)
129            }
130            Stake { amount, public_key } => {
131                crate::env::promise_batch_action_stake(promise_index, *amount, public_key)
132            }
133            AddFullAccessKey { public_key, nonce } => {
134                crate::env::promise_batch_action_add_key_with_full_access(
135                    promise_index,
136                    public_key,
137                    *nonce,
138                )
139            }
140            AddAccessKey { public_key, allowance, receiver_id, function_names, nonce } => {
141                crate::env::promise_batch_action_add_key_allowance_with_function_call(
142                    promise_index,
143                    public_key,
144                    *nonce,
145                    *allowance,
146                    receiver_id,
147                    function_names,
148                )
149            }
150            DeleteKey { public_key } => {
151                crate::env::promise_batch_action_delete_key(promise_index, public_key)
152            }
153            DeleteAccount { beneficiary_id } => {
154                crate::env::promise_batch_action_delete_account(promise_index, beneficiary_id)
155            }
156            #[cfg(feature = "global-contracts")]
157            DeployGlobalContract { code } => {
158                crate::env::promise_batch_action_deploy_global_contract(promise_index, code)
159            }
160            #[cfg(feature = "global-contracts")]
161            DeployGlobalContractByAccountId { code } => {
162                crate::env::promise_batch_action_deploy_global_contract_by_account_id(
163                    promise_index,
164                    code,
165                )
166            }
167            #[cfg(feature = "global-contracts")]
168            UseGlobalContract { code_hash } => {
169                crate::env::promise_batch_action_use_global_contract(promise_index, code_hash)
170            }
171            #[cfg(feature = "global-contracts")]
172            UseGlobalContractByAccountId { account_id } => {
173                crate::env::promise_batch_action_use_global_contract_by_account_id(
174                    promise_index,
175                    account_id,
176                )
177            }
178            #[cfg(feature = "deterministic-account-ids")]
179            DeterministicStateInit {
180                state_init: crate::state_init::StateInit::V1(state_init),
181                deposit,
182            } => {
183                use crate::GlobalContractId;
184
185                let action_index = match &state_init.code {
186                    GlobalContractId::CodeHash(code_hash) => {
187                        crate::env::promise_batch_action_state_init(
188                            promise_index,
189                            *code_hash,
190                            *deposit,
191                        )
192                    }
193                    GlobalContractId::AccountId(account_id) => {
194                        crate::env::promise_batch_action_state_init_by_account_id(
195                            promise_index,
196                            account_id,
197                            *deposit,
198                        )
199                    }
200                };
201                for (key, value) in &state_init.data {
202                    crate::env::set_state_init_data_entry(promise_index, action_index, key, value);
203                }
204            }
205        }
206    }
207}
208
209struct PromiseSingle {
210    pub account_id: AccountId,
211    pub actions: RefCell<Vec<PromiseAction>>,
212    pub after: RefCell<Option<Rc<Promise>>>,
213    /// Promise index that is computed only once.
214    pub promise_index: RefCell<Option<PromiseIndex>>,
215}
216
217impl PromiseSingle {
218    pub fn construct_recursively(&self) -> PromiseIndex {
219        let mut promise_lock = self.promise_index.borrow_mut();
220        if let Some(res) = promise_lock.as_ref() {
221            return *res;
222        }
223        let promise_index = if let Some(after) =
224            self.after.borrow().as_deref().and_then(Promise::construct_recursively)
225        {
226            crate::env::promise_batch_then(after, &self.account_id)
227        } else {
228            crate::env::promise_batch_create(&self.account_id)
229        };
230        let actions_lock = self.actions.borrow();
231        for action in actions_lock.iter() {
232            action.add(promise_index);
233        }
234        *promise_lock = Some(promise_index);
235        promise_index
236    }
237}
238
239pub struct PromiseJoint {
240    pub promises: RefCell<VecDeque<Promise>>,
241    /// Promise index that is computed only once.
242    pub promise_index: RefCell<Option<PromiseIndex>>,
243}
244
245impl PromiseJoint {
246    pub fn construct_recursively(&self) -> Option<PromiseIndex> {
247        let mut promise_lock = self.promise_index.borrow_mut();
248        if let Some(res) = promise_lock.as_ref() {
249            return Some(*res);
250        }
251        let promises_lock = self.promises.borrow();
252        if promises_lock.is_empty() {
253            return None;
254        }
255        let res = crate::env::promise_and(
256            &promises_lock.iter().filter_map(Promise::construct_recursively).collect::<Vec<_>>(),
257        );
258        *promise_lock = Some(res);
259        Some(res)
260    }
261}
262
263/// A structure representing a result of the scheduled execution on another contract.
264///
265/// Smart contract developers will explicitly use `Promise` in two situations:
266/// * When they need to return `Promise`.
267///
268///   In the following code if someone calls method `ContractA::a` they will internally cause an
269///   execution of method `ContractB::b` of `bob_near` account, and the return value of `ContractA::a`
270///   will be what `ContractB::b` returned.
271/// ```no_run
272/// # use near_sdk::{ext_contract, near, Promise, Gas};
273/// #[ext_contract]
274/// pub trait ContractB {
275///     fn b(&mut self);
276/// }
277///
278/// #[near(contract_state)]
279/// #[derive(Default)]
280/// struct ContractA {}
281///
282/// #[near]
283/// impl ContractA {
284///     pub fn a(&self) -> Promise {
285///         contract_b::ext("bob_near".parse().unwrap()).b()
286///     }
287/// }
288/// ```
289///
290/// * When they need to create a transaction with one or many actions, e.g. the following code
291///   schedules a transaction that creates an account, transfers tokens, and assigns a public key:
292///
293/// ```no_run
294/// # use near_sdk::{Promise, env, test_utils::VMContextBuilder, testing_env, Gas, NearToken};
295/// # testing_env!(VMContextBuilder::new().signer_account_id("bob_near".parse().unwrap())
296/// #               .account_balance(NearToken::from_yoctonear(1000)).prepaid_gas(Gas::from_gas(1_000_000)).build());
297/// Promise::new("bob_near".parse().unwrap())
298///   .create_account()
299///   .transfer(NearToken::from_yoctonear(1000))
300///   .add_full_access_key(env::signer_account_pk());
301/// ```
302///
303/// More information about promises in [NEAR documentation](https://docs.near.org/build/smart-contracts/anatomy/crosscontract#promises)
304#[must_use = "return or detach explicitly via `.detach()`"]
305pub struct Promise {
306    subtype: PromiseSubtype,
307    should_return: RefCell<bool>,
308}
309
310/// Until we implement strongly typed promises we serialize them as unit struct.
311#[cfg(feature = "abi")]
312impl BorshSchema for Promise {
313    fn add_definitions_recursively(
314        definitions: &mut BTreeMap<borsh::schema::Declaration, borsh::schema::Definition>,
315    ) {
316        <()>::add_definitions_recursively(definitions);
317    }
318
319    fn declaration() -> borsh::schema::Declaration {
320        <()>::declaration()
321    }
322}
323
324#[derive(Clone)]
325enum PromiseSubtype {
326    Single(Rc<PromiseSingle>),
327    Joint(Rc<PromiseJoint>),
328}
329
330impl Promise {
331    /// Create a promise that acts on the given account.
332    /// Uses low-level [`crate::env::promise_batch_create`]
333    pub fn new(account_id: AccountId) -> Self {
334        Self {
335            subtype: PromiseSubtype::Single(Rc::new(PromiseSingle {
336                account_id,
337                actions: RefCell::new(vec![]),
338                after: RefCell::new(None),
339                promise_index: RefCell::new(None),
340            })),
341            should_return: RefCell::new(false),
342        }
343    }
344
345    fn add_action(self, action: PromiseAction) -> Self {
346        match &self.subtype {
347            PromiseSubtype::Single(x) => x.actions.borrow_mut().push(action),
348            PromiseSubtype::Joint(_) => {
349                crate::env::panic_str("Cannot add action to a joint promise.")
350            }
351        }
352        self
353    }
354
355    /// Create account on which this promise acts.
356    /// Uses low-level [`crate::env::promise_batch_action_create_account`]
357    pub fn create_account(self) -> Self {
358        self.add_action(PromiseAction::CreateAccount)
359    }
360
361    /// Deploy a smart contract to the account on which this promise acts.
362    /// Uses low-level [`crate::env::promise_batch_action_deploy_contract`]
363    pub fn deploy_contract(self, code: impl Into<Vec<u8>>) -> Self {
364        self.add_action(PromiseAction::DeployContract { code: code.into() })
365    }
366
367    #[cfg(feature = "global-contracts")]
368    /// Deploy a global smart contract using the provided contract code.
369    /// Uses low-level [`crate::env::promise_batch_action_deploy_global_contract`]
370    ///
371    /// # Examples
372    /// ```no_run
373    /// use near_sdk::{Promise, NearToken};
374    ///
375    /// let code = vec![0u8; 100]; // Contract bytecode
376    /// Promise::new("alice.near".parse().unwrap())
377    ///     .create_account()
378    ///     .transfer(NearToken::from_yoctonear(1000))
379    ///     .deploy_global_contract(code);
380    /// ```
381    pub fn deploy_global_contract(self, code: impl Into<Vec<u8>>) -> Self {
382        self.add_action(PromiseAction::DeployGlobalContract { code: code.into() })
383    }
384
385    #[cfg(feature = "global-contracts")]
386    /// Deploy a global smart contract, identifiable by the predecessor's account ID.
387    /// Uses low-level [`crate::env::promise_batch_action_deploy_global_contract_by_account_id`]
388    ///
389    /// # Examples
390    /// ```no_run
391    /// use near_sdk::{Promise, NearToken};
392    ///
393    /// let code = vec![0u8; 100]; // Contract bytecode
394    /// Promise::new("alice.near".parse().unwrap())
395    ///     .create_account()
396    ///     .transfer(NearToken::from_yoctonear(1000))
397    ///     .deploy_global_contract_by_account_id(code);
398    /// ```
399    pub fn deploy_global_contract_by_account_id(self, code: impl Into<Vec<u8>>) -> Self {
400        self.add_action(PromiseAction::DeployGlobalContractByAccountId { code: code.into() })
401    }
402
403    #[cfg(feature = "global-contracts")]
404    /// Use an existing global contract by code hash.
405    /// Uses low-level [`crate::env::promise_batch_action_use_global_contract`]
406    ///
407    /// # Examples
408    /// ```no_run
409    /// use near_sdk::{Promise, NearToken};
410    ///
411    /// let code_hash = [0u8; 32]; // 32-byte hash (CryptoHash)
412    /// Promise::new("alice.near".parse().unwrap())
413    ///     .create_account()
414    ///     .transfer(NearToken::from_yoctonear(1000))
415    ///     .use_global_contract(code_hash);
416    /// ```
417    pub fn use_global_contract(self, code_hash: impl Into<CryptoHash>) -> Self {
418        self.add_action(PromiseAction::UseGlobalContract { code_hash: code_hash.into() })
419    }
420
421    #[cfg(feature = "global-contracts")]
422    /// Use an existing global contract by referencing the account that deployed it.
423    /// Uses low-level [`crate::env::promise_batch_action_use_global_contract_by_account_id`]
424    ///
425    /// # Examples
426    /// ```no_run
427    /// use near_sdk::{Promise, NearToken, AccountId};
428    ///
429    /// Promise::new("alice.near".parse().unwrap())
430    ///     .create_account()
431    ///     .transfer(NearToken::from_yoctonear(1000))
432    ///     .use_global_contract_by_account_id("deployer.near".parse().unwrap());
433    /// ```
434    pub fn use_global_contract_by_account_id(self, account_id: AccountId) -> Self {
435        self.add_action(PromiseAction::UseGlobalContractByAccountId { account_id })
436    }
437
438    /// Creates a deterministic account with the given code, deposit, and data.
439    #[cfg(feature = "deterministic-account-ids")]
440    pub fn state_init(self, state_init: crate::state_init::StateInit, deposit: NearToken) -> Self {
441        self.add_action(PromiseAction::DeterministicStateInit { state_init, deposit })
442    }
443
444    /// A low-level interface for making a function call to the account that this promise acts on.
445    /// Uses low-level [`crate::env::promise_batch_action_function_call`]
446    pub fn function_call(
447        self,
448        function_name: impl Into<String>,
449        arguments: impl Into<Vec<u8>>,
450        amount: NearToken,
451        gas: Gas,
452    ) -> Self {
453        self.add_action(PromiseAction::FunctionCall {
454            function_name: function_name.into(),
455            arguments: arguments.into(),
456            amount,
457            gas,
458        })
459    }
460
461    /// A low-level interface for making a function call to the account that this promise acts on.
462    /// unlike [`Promise::function_call`], this function accepts a weight to use relative unused gas
463    /// on this function call at the end of the scheduling method execution.
464    /// Uses low-level [`crate::env::promise_batch_action_function_call_weight`]
465    pub fn function_call_weight(
466        self,
467        function_name: impl Into<String>,
468        arguments: impl Into<Vec<u8>>,
469        amount: NearToken,
470        gas: Gas,
471        weight: GasWeight,
472    ) -> Self {
473        self.add_action(PromiseAction::FunctionCallWeight {
474            function_name: function_name.into(),
475            arguments: arguments.into(),
476            amount,
477            gas,
478            weight,
479        })
480    }
481
482    /// Transfer tokens to the account that this promise acts on.
483    /// Uses low-level [`crate::env::promise_batch_action_transfer`]
484    pub fn transfer(self, amount: NearToken) -> Self {
485        self.add_action(PromiseAction::Transfer { amount })
486    }
487
488    /// Stake the account for the given amount of tokens using the given public key.
489    /// Uses low-level [`crate::env::promise_batch_action_stake`]
490    pub fn stake(self, amount: NearToken, public_key: PublicKey) -> Self {
491        self.add_action(PromiseAction::Stake { amount, public_key })
492    }
493
494    /// Add full access key to the given account.
495    /// Uses low-level [`crate::env::promise_batch_action_add_key_with_full_access`]
496    pub fn add_full_access_key(self, public_key: PublicKey) -> Self {
497        self.add_full_access_key_with_nonce(public_key, 0)
498    }
499
500    /// Add full access key to the given account with a provided nonce.
501    /// Uses low-level [`crate::env::promise_batch_action_add_key_with_full_access`]
502    pub fn add_full_access_key_with_nonce(self, public_key: PublicKey, nonce: u64) -> Self {
503        self.add_action(PromiseAction::AddFullAccessKey { public_key, nonce })
504    }
505
506    /// Add an access key that is restricted to only calling a smart contract on some account using
507    /// only a restricted set of methods. Here `function_names` is a comma separated list of methods,
508    /// e.g. `"method_a,method_b"`.
509    /// Uses low-level [`crate::env::promise_batch_action_add_key_allowance_with_function_call`]
510    pub fn add_access_key_allowance(
511        self,
512        public_key: PublicKey,
513        allowance: Allowance,
514        receiver_id: AccountId,
515        function_names: impl Into<String>,
516    ) -> Self {
517        self.add_access_key_allowance_with_nonce(
518            public_key,
519            allowance,
520            receiver_id,
521            function_names,
522            0,
523        )
524    }
525
526    #[deprecated(since = "5.0.0", note = "Use add_access_key_allowance instead")]
527    pub fn add_access_key(
528        self,
529        public_key: PublicKey,
530        allowance: NearToken,
531        receiver_id: AccountId,
532        function_names: impl Into<String>,
533    ) -> Self {
534        let allowance = migrate_to_allowance(allowance);
535        self.add_access_key_allowance(public_key, allowance, receiver_id, function_names)
536    }
537
538    /// Add an access key with a provided nonce.
539    /// Uses low-level [`crate::env::promise_batch_action_add_key_allowance_with_function_call`]
540    pub fn add_access_key_allowance_with_nonce(
541        self,
542        public_key: PublicKey,
543        allowance: Allowance,
544        receiver_id: AccountId,
545        function_names: impl Into<String>,
546        nonce: u64,
547    ) -> Self {
548        self.add_action(PromiseAction::AddAccessKey {
549            public_key,
550            allowance,
551            receiver_id,
552            function_names: function_names.into(),
553            nonce,
554        })
555    }
556
557    #[deprecated(since = "5.0.0", note = "Use add_access_key_allowance_with_nonce instead")]
558    pub fn add_access_key_with_nonce(
559        self,
560        public_key: PublicKey,
561        allowance: NearToken,
562        receiver_id: AccountId,
563        function_names: impl Into<String>,
564        nonce: u64,
565    ) -> Self {
566        let allowance = migrate_to_allowance(allowance);
567        self.add_access_key_allowance_with_nonce(
568            public_key,
569            allowance,
570            receiver_id,
571            function_names,
572            nonce,
573        )
574    }
575
576    /// Delete access key from the given account.
577    /// Uses low-level [`crate::env::promise_batch_action_delete_key`]
578    pub fn delete_key(self, public_key: PublicKey) -> Self {
579        self.add_action(PromiseAction::DeleteKey { public_key })
580    }
581
582    /// Delete the given account.
583    /// Uses low-level [`crate::env::promise_batch_action_delete_account`]
584    pub fn delete_account(self, beneficiary_id: AccountId) -> Self {
585        self.add_action(PromiseAction::DeleteAccount { beneficiary_id })
586    }
587
588    /// Merge this promise with another promise, so that we can schedule execution of another
589    /// smart contract right after all merged promises finish.
590    ///
591    /// Note, once the promises are merged it is not possible to add actions to them, e.g. the
592    /// following code will panic during the execution of the smart contract:
593    ///
594    /// ```no_run
595    /// # use near_sdk::{Promise, testing_env};
596    /// let p1 = Promise::new("bob_near".parse().unwrap()).create_account();
597    /// let p2 = Promise::new("carol_near".parse().unwrap()).create_account();
598    /// let p3 = p1.and(p2);
599    /// // p3.create_account();
600    /// ```
601    /// Uses low-level [`crate::env::promise_and`]
602    pub fn and(self, other: Promise) -> Promise {
603        match (&self.subtype, &other.subtype) {
604            (PromiseSubtype::Joint(x), PromiseSubtype::Joint(o)) => {
605                x.promises.borrow_mut().append(&mut o.promises.borrow_mut());
606                self
607            }
608            (PromiseSubtype::Joint(x), _) => {
609                x.promises.borrow_mut().push_back(other);
610                self
611            }
612            (_, PromiseSubtype::Joint(o)) => {
613                o.promises.borrow_mut().push_front(self);
614                other
615            }
616            _ => Promise {
617                subtype: PromiseSubtype::Joint(Rc::new(PromiseJoint {
618                    promises: RefCell::new([self, other].into()),
619                    promise_index: RefCell::new(None),
620                })),
621                should_return: RefCell::new(false),
622            },
623        }
624    }
625
626    /// Schedules execution of another promise right after the current promise finish executing.
627    ///
628    /// In the following code `bob_near` and `dave_near` will be created concurrently. `carol_near`
629    /// creation will wait for `bob_near` to be created, and `eva_near` will wait for both `carol_near`
630    /// and `dave_near` to be created first.
631    /// ```no_run
632    /// # use near_sdk::{Promise, VMContext, testing_env};
633    /// let p1 = Promise::new("bob_near".parse().unwrap()).create_account();
634    /// let p2 = Promise::new("carol_near".parse().unwrap()).create_account();
635    /// let p3 = Promise::new("dave_near".parse().unwrap()).create_account();
636    /// let p4 = Promise::new("eva_near".parse().unwrap()).create_account();
637    /// p1.then(p2).and(p3).then(p4);
638    /// ```
639    /// Uses low-level [`crate::env::promise_batch_then`]
640    pub fn then(self, other: Promise) -> Promise {
641        Rc::new(self).then_impl(other)
642    }
643
644    /// Shared, private implementation between `then` and `then_concurrent`.
645    ///
646    /// This takes self as a reference counted object promise, to allow multiple
647    /// dependencies pointing to the same promise without duplicating that
648    /// promise.
649    fn then_impl(self: Rc<Self>, other: Promise) -> Promise {
650        match &other.subtype {
651            PromiseSubtype::Single(x) => {
652                let mut after = x.after.borrow_mut();
653                if after.is_some() {
654                    crate::env::panic_str(
655                        "Cannot callback promise which is already scheduled after another",
656                    );
657                }
658                *after = Some(self)
659            }
660            PromiseSubtype::Joint(_) => crate::env::panic_str("Cannot callback joint promise."),
661        }
662        other
663    }
664
665    /// Schedules execution of multiple concurrent promises right after the
666    /// current promise finishes executing.
667    ///
668    /// This method will send the same return value as a data receipt to all
669    /// following receipts.
670    ///
671    /// In the following code, `bob_near` is created first, `carol_near` second,
672    /// and finally `dave_near` and `eva_near` are created concurrently.
673    ///
674    /// ```no_run
675    /// # use near_sdk::Promise;
676    /// let p1 = Promise::new("bob_near".parse().unwrap()).create_account();
677    /// let p2 = Promise::new("carol_near".parse().unwrap()).create_account();
678    /// let p3 = Promise::new("dave_near".parse().unwrap()).create_account();
679    /// let p4 = Promise::new("eva_near".parse().unwrap()).create_account();
680    /// p1.then(p2).then_concurrent(vec![p3, p4]);
681    /// ```
682    ///
683    /// The returned [`ConcurrentPromises`] allows chaining more promises.
684    ///
685    /// In the following code, `bob_near` is created first, next `carol_near`
686    /// and `dave_near` are created concurrently, and finally `eva_near` is
687    /// created after all others have been created.
688    ///
689    /// ```no_run
690    /// # use near_sdk::Promise;
691    /// let p1 = Promise::new("bob_near".parse().unwrap()).create_account();
692    /// let p2 = Promise::new("carol_near".parse().unwrap()).create_account();
693    /// let p3 = Promise::new("dave_near".parse().unwrap()).create_account();
694    /// let p4 = Promise::new("eva_near".parse().unwrap()).create_account();
695    /// p1.then_concurrent(vec![p2, p3]).join().then(p4);
696    /// ```
697    pub fn then_concurrent(
698        self,
699        promises: impl IntoIterator<Item = Promise>,
700    ) -> ConcurrentPromises {
701        let this = Rc::new(self);
702        let mapped_promises =
703            promises.into_iter().map(|other| Rc::clone(&this).then_impl(other)).collect();
704        ConcurrentPromises { promises: mapped_promises }
705    }
706
707    /// A specialized, relatively low-level API method. Allows to mark the given promise as the one
708    /// that should be considered as a return value.
709    ///
710    /// In the below code `a1` and `a2` functions are equivalent.
711    /// ```
712    /// # use near_sdk::{ext_contract, Gas, near, Promise};
713    /// #[ext_contract]
714    /// pub trait ContractB {
715    ///     fn b(&mut self);
716    /// }
717    ///
718    /// #[near(contract_state)]
719    /// #[derive(Default)]
720    /// struct ContractA {}
721    ///
722    /// #[near]
723    /// impl ContractA {
724    ///     pub fn a1(&self) {
725    ///        contract_b::ext("bob_near".parse().unwrap()).b().as_return();
726    ///     }
727    ///
728    ///     pub fn a2(&self) -> Promise {
729    ///        contract_b::ext("bob_near".parse().unwrap()).b()
730    ///     }
731    /// }
732    /// ```
733    /// Makes the promise to use low-level [`crate::env::promise_return`].
734    #[allow(clippy::wrong_self_convention)]
735    pub fn as_return(self) -> Self {
736        *self.should_return.borrow_mut() = true;
737        self
738    }
739
740    fn construct_recursively(&self) -> Option<PromiseIndex> {
741        let res = match &self.subtype {
742            PromiseSubtype::Single(x) => x.construct_recursively(),
743            PromiseSubtype::Joint(x) => x.construct_recursively()?,
744        };
745        if *self.should_return.borrow() {
746            crate::env::promise_return(res);
747        }
748        Some(res)
749    }
750
751    /// Explicitly detach given promise
752    #[inline]
753    pub fn detach(self) {}
754}
755
756impl Drop for Promise {
757    fn drop(&mut self) {
758        self.construct_recursively();
759    }
760}
761
762impl serde::Serialize for Promise {
763    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
764    where
765        S: serde::Serializer,
766    {
767        *self.should_return.borrow_mut() = true;
768        serializer.serialize_unit()
769    }
770}
771
772impl borsh::BorshSerialize for Promise {
773    fn serialize<W: Write>(&self, _writer: &mut W) -> Result<(), Error> {
774        *self.should_return.borrow_mut() = true;
775
776        // Intentionally no bytes written for the promise, the return value from the promise
777        // will be considered as the return value from the contract call.
778        Ok(())
779    }
780}
781
782#[cfg(feature = "abi")]
783impl schemars::JsonSchema for Promise {
784    fn schema_name() -> String {
785        "Promise".to_string()
786    }
787
788    fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
789        // Since promises are untyped, for now we represent Promise results with the schema
790        // `true` which matches everything (i.e. always passes validation)
791        schemars::schema::Schema::Bool(true)
792    }
793}
794
795/// When the method can return either a promise or a value, it can be called with `PromiseOrValue::Promise`
796/// or `PromiseOrValue::Value` to specify which one should be returned.
797/// # Example
798/// ```no_run
799/// # use near_sdk::{ext_contract, near, Gas, PromiseOrValue};
800/// #[ext_contract]
801/// pub trait ContractA {
802///     fn a(&mut self);
803/// }
804///
805/// let value = Some(true);
806/// let val: PromiseOrValue<bool> = if let Some(value) = value {
807///     PromiseOrValue::Value(value)
808/// } else {
809///     contract_a::ext("bob_near".parse().unwrap()).a().into()
810/// };
811/// ```
812#[must_use = "return or detach explicitly via `.detach()`"]
813#[derive(serde::Serialize)]
814#[serde(untagged)]
815pub enum PromiseOrValue<T> {
816    Promise(Promise),
817    Value(T),
818}
819
820impl<T> PromiseOrValue<T> {
821    /// Explicitly detach if it was a promise
822    #[inline]
823    pub fn detach(self) {}
824}
825
826#[cfg(feature = "abi")]
827impl<T> BorshSchema for PromiseOrValue<T>
828where
829    T: BorshSchema,
830{
831    fn add_definitions_recursively(
832        definitions: &mut BTreeMap<borsh::schema::Declaration, borsh::schema::Definition>,
833    ) {
834        T::add_definitions_recursively(definitions);
835    }
836
837    fn declaration() -> borsh::schema::Declaration {
838        T::declaration()
839    }
840}
841
842impl<T> From<Promise> for PromiseOrValue<T> {
843    fn from(promise: Promise) -> Self {
844        PromiseOrValue::Promise(promise)
845    }
846}
847
848impl<T: borsh::BorshSerialize> borsh::BorshSerialize for PromiseOrValue<T> {
849    fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
850        match self {
851            // Only actual value is serialized.
852            PromiseOrValue::Value(x) => x.serialize(writer),
853            // The promise is dropped to cause env::promise calls.
854            PromiseOrValue::Promise(p) => p.serialize(writer),
855        }
856    }
857}
858
859#[cfg(feature = "abi")]
860impl<T: schemars::JsonSchema> schemars::JsonSchema for PromiseOrValue<T> {
861    fn schema_name() -> String {
862        format!("PromiseOrValue{}", T::schema_name())
863    }
864
865    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
866        T::json_schema(gen)
867    }
868}
869
870/// A list of promises that are executed concurrently.
871///
872/// This is the return type of [`Promise::then_concurrent`] and it wraps the
873/// contained promises for a more convenient syntax when chaining calls.
874///
875/// Use [`ConcurrentPromises::join`] to create a new promises that waits for all
876/// concurrent promises and takes all their return values as inputs.
877///
878/// Use [`ConcurrentPromises::split_off`] to divide the list of promises into
879/// subgroups that can be joined independently.
880#[must_use = "return or detach explicitly via `.detach()`"]
881pub struct ConcurrentPromises {
882    promises: Vec<Promise>,
883}
884
885impl ConcurrentPromises {
886    /// Create a new promises that waits for all contained concurrent promises.
887    ///
888    /// The returned promise is a [`Promise::and`] combination of all contained
889    /// promises. Chain it with a [`Promise::then`] to wait for them to finish
890    /// and receive all their outputs as inputs in a following function call.
891    pub fn join(self) -> Promise {
892        self.promises
893            .into_iter()
894            .reduce(|left, right| left.and(right))
895            .expect("cannot join empty concurrent promises")
896    }
897
898    /// Splits the contained list of promises into two at the given index,
899    /// returning a new [`ConcurrentPromises`] containing the elements in the
900    /// range [at, len).
901    ///
902    /// After the call, the original [`ConcurrentPromises`] will be left
903    /// containing the elements [0, at).
904    pub fn split_off(&mut self, at: usize) -> ConcurrentPromises {
905        let right_side = self.promises.split_off(at);
906        ConcurrentPromises { promises: right_side }
907    }
908
909    /// Explicitly detach given promises
910    #[inline]
911    pub fn detach(self) {}
912}
913
914#[cfg(not(target_arch = "wasm32"))]
915#[cfg(test)]
916mod tests {
917    use crate::mock::MockAction;
918    use crate::test_utils::get_created_receipts;
919    use crate::test_utils::test_env::{alice, bob};
920    use crate::{
921        test_utils::VMContextBuilder, testing_env, AccountId, Allowance, NearToken, Promise,
922        PublicKey,
923    };
924
925    fn pk() -> PublicKey {
926        "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp".parse().unwrap()
927    }
928
929    fn get_actions() -> std::vec::IntoIter<MockAction> {
930        let receipts = get_created_receipts();
931        let first_receipt = receipts.into_iter().next().unwrap();
932        first_receipt.actions.into_iter()
933    }
934
935    fn has_add_key_with_full_access(public_key: PublicKey, nonce: Option<u64>) -> bool {
936        let public_key = near_crypto::PublicKey::try_from(public_key).unwrap();
937        get_actions().any(|el| {
938            matches!(
939                el,
940                MockAction::AddKeyWithFullAccess { public_key: p, nonce: n, receipt_index: _, }
941                if p == public_key
942                    && (nonce.is_none() || Some(n) == nonce)
943            )
944        })
945    }
946
947    fn has_add_key_with_function_call(
948        public_key: PublicKey,
949        allowance: u128,
950        receiver_id: AccountId,
951        function_names: String,
952        nonce: Option<u64>,
953    ) -> bool {
954        let public_key = near_crypto::PublicKey::try_from(public_key).unwrap();
955        get_actions().any(|el| {
956            matches!(
957                el,
958                MockAction::AddKeyWithFunctionCall {
959                    public_key: p,
960                    allowance: a,
961                    receiver_id: r,
962                    method_names,
963                    nonce: n,
964                    receipt_index: _,
965                }
966                if p == public_key
967                    && a.unwrap() == NearToken::from_yoctonear(allowance)
968                    && r == receiver_id
969                    && method_names.clone() == function_names.split(',').collect::<Vec<_>>()
970                    && (nonce.is_none() || Some(n) == nonce)
971            )
972        })
973    }
974
975    #[test]
976    fn test_add_full_access_key() {
977        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
978
979        let public_key: PublicKey = pk();
980
981        // Promise is only executed when dropped so we put it in its own scope to make sure receipts
982        // are ready afterwards.
983        {
984            Promise::new(alice()).create_account().add_full_access_key(public_key.clone()).detach();
985        }
986
987        assert!(has_add_key_with_full_access(public_key, None));
988    }
989
990    #[test]
991    fn test_add_full_access_key_with_nonce() {
992        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
993
994        let public_key: PublicKey = pk();
995        let nonce = 42;
996
997        {
998            Promise::new(alice())
999                .create_account()
1000                .add_full_access_key_with_nonce(public_key.clone(), nonce)
1001                .detach();
1002        }
1003
1004        assert!(has_add_key_with_full_access(public_key, Some(nonce)));
1005    }
1006
1007    #[test]
1008    fn test_add_access_key_allowance() {
1009        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1010
1011        let public_key: PublicKey = pk();
1012        let allowance = 100;
1013        let receiver_id = bob();
1014        let function_names = "method_a,method_b".to_string();
1015
1016        {
1017            Promise::new(alice())
1018                .create_account()
1019                .add_access_key_allowance(
1020                    public_key.clone(),
1021                    Allowance::Limited(allowance.try_into().unwrap()),
1022                    receiver_id.clone(),
1023                    function_names.clone(),
1024                )
1025                .detach();
1026        }
1027
1028        assert!(has_add_key_with_function_call(
1029            public_key,
1030            allowance,
1031            receiver_id,
1032            function_names,
1033            None
1034        ));
1035    }
1036
1037    #[test]
1038    fn test_add_access_key() {
1039        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1040
1041        let public_key: PublicKey = pk();
1042        let allowance = NearToken::from_yoctonear(100);
1043        let receiver_id = bob();
1044        let function_names = "method_a,method_b".to_string();
1045
1046        {
1047            #[allow(deprecated)]
1048            Promise::new(alice())
1049                .create_account()
1050                .add_access_key(
1051                    public_key.clone(),
1052                    allowance,
1053                    receiver_id.clone(),
1054                    function_names.clone(),
1055                )
1056                .detach();
1057        }
1058
1059        assert!(has_add_key_with_function_call(
1060            public_key,
1061            allowance.as_yoctonear(),
1062            receiver_id,
1063            function_names,
1064            None
1065        ));
1066    }
1067
1068    #[test]
1069    fn test_add_access_key_allowance_with_nonce() {
1070        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1071
1072        let public_key: PublicKey = pk();
1073        let allowance = 100;
1074        let receiver_id = bob();
1075        let function_names = "method_a,method_b".to_string();
1076        let nonce = 42;
1077
1078        {
1079            Promise::new(alice())
1080                .create_account()
1081                .add_access_key_allowance_with_nonce(
1082                    public_key.clone(),
1083                    Allowance::Limited(allowance.try_into().unwrap()),
1084                    receiver_id.clone(),
1085                    function_names.clone(),
1086                    nonce,
1087                )
1088                .detach();
1089        }
1090
1091        assert!(has_add_key_with_function_call(
1092            public_key,
1093            allowance,
1094            receiver_id,
1095            function_names,
1096            Some(nonce)
1097        ));
1098    }
1099
1100    #[test]
1101    fn test_add_access_key_with_nonce() {
1102        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1103
1104        let public_key: PublicKey = pk();
1105        let allowance = NearToken::from_yoctonear(100);
1106        let receiver_id = bob();
1107        let function_names = "method_a,method_b".to_string();
1108        let nonce = 42;
1109
1110        {
1111            #[allow(deprecated)]
1112            Promise::new(alice())
1113                .create_account()
1114                .add_access_key_with_nonce(
1115                    public_key.clone(),
1116                    allowance,
1117                    receiver_id.clone(),
1118                    function_names.clone(),
1119                    nonce,
1120                )
1121                .detach();
1122        }
1123
1124        assert!(has_add_key_with_function_call(
1125            public_key,
1126            allowance.as_yoctonear(),
1127            receiver_id,
1128            function_names,
1129            Some(nonce)
1130        ));
1131    }
1132
1133    #[test]
1134    fn test_delete_key() {
1135        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1136
1137        let public_key: PublicKey = pk();
1138
1139        {
1140            Promise::new(alice())
1141                .create_account()
1142                .add_full_access_key(public_key.clone())
1143                .delete_key(public_key.clone())
1144                .detach();
1145        }
1146        let public_key = near_crypto::PublicKey::try_from(public_key).unwrap();
1147
1148        let has_action = get_actions().any(|el| {
1149            matches!(
1150                el,
1151                MockAction::DeleteKey { public_key: p , receipt_index: _, } if p == public_key
1152            )
1153        });
1154        assert!(has_action);
1155    }
1156
1157    #[cfg(feature = "global-contracts")]
1158    #[test]
1159    fn test_deploy_global_contract() {
1160        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1161
1162        let code = vec![1, 2, 3, 4];
1163
1164        {
1165            Promise::new(alice()).create_account().deploy_global_contract(code.clone()).detach();
1166        }
1167
1168        let has_action = get_actions().any(|el| {
1169            matches!(
1170                el,
1171                MockAction::DeployGlobalContract { code: c, receipt_index: _, mode: _ } if c == code
1172            )
1173        });
1174        assert!(has_action);
1175    }
1176
1177    #[cfg(feature = "global-contracts")]
1178    #[test]
1179    fn test_deploy_global_contract_by_account_id() {
1180        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1181
1182        let code = vec![5, 6, 7, 8];
1183
1184        {
1185            Promise::new(alice())
1186                .create_account()
1187                .deploy_global_contract_by_account_id(code.clone())
1188                .detach();
1189        }
1190
1191        let has_action = get_actions().any(|el| {
1192            matches!(
1193                el,
1194                MockAction::DeployGlobalContract { code: c, receipt_index: _, mode: _ } if c == code
1195            )
1196        });
1197        assert!(has_action);
1198    }
1199
1200    #[cfg(feature = "global-contracts")]
1201    #[test]
1202    fn test_use_global_contract() {
1203        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1204
1205        let code_hash = [0u8; 32];
1206
1207        {
1208            Promise::new(alice()).create_account().use_global_contract(code_hash).detach();
1209        }
1210
1211        // Check if any UseGlobalContract action exists
1212        let has_action = get_actions().any(|el| matches!(el, MockAction::UseGlobalContract { .. }));
1213        assert!(has_action);
1214    }
1215
1216    #[cfg(feature = "global-contracts")]
1217    #[test]
1218    fn test_use_global_contract_by_account_id() {
1219        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1220
1221        let deployer = bob();
1222
1223        {
1224            Promise::new(alice())
1225                .create_account()
1226                .use_global_contract_by_account_id(deployer.clone())
1227                .detach();
1228        }
1229
1230        let has_action = get_actions().any(|el| {
1231            matches!(
1232                el,
1233                MockAction::UseGlobalContract {
1234                    contract_id: near_primitives::action::GlobalContractIdentifier::AccountId(contract_id),
1235                    receipt_index: _
1236                }
1237                if contract_id == deployer
1238            )
1239        });
1240        assert!(has_action);
1241    }
1242
1243    #[test]
1244    fn test_then() {
1245        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1246        let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1247
1248        {
1249            Promise::new(alice())
1250                .create_account()
1251                .then(Promise::new(sub_account_1).create_account())
1252                .detach();
1253        }
1254
1255        let receipts = get_created_receipts();
1256        let main_account_creation = &receipts[0];
1257        let sub_creation = &receipts[1];
1258
1259        assert!(
1260            main_account_creation.receipt_indices.is_empty(),
1261            "first receipt must not have dependencies"
1262        );
1263        assert_eq!(
1264            &sub_creation.receipt_indices,
1265            &[0],
1266            "then_concurrent() must create dependency on receipt 0"
1267        );
1268    }
1269
1270    #[test]
1271    fn test_then_concurrent() {
1272        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1273        let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1274        let sub_account_2: AccountId = "sub2.alice.near".parse().unwrap();
1275
1276        {
1277            let p1 = Promise::new(sub_account_1.clone()).create_account();
1278            let p2 = Promise::new(sub_account_2.clone()).create_account();
1279            Promise::new(alice()).create_account().then_concurrent(vec![p1, p2]).detach();
1280        }
1281
1282        let receipts = get_created_receipts();
1283        let main_account_creation = &receipts[0];
1284
1285        let sub1_creation = &receipts[1];
1286        let sub2_creation = &receipts[2];
1287
1288        // ensure we are looking at the right receipts
1289        assert_eq!(sub1_creation.receiver_id, sub_account_1);
1290        assert_eq!(sub2_creation.receiver_id, sub_account_2);
1291
1292        // Check dependencies were created
1293        assert!(
1294            main_account_creation.receipt_indices.is_empty(),
1295            "first receipt must not have dependencies"
1296        );
1297        assert_eq!(
1298            &sub1_creation.receipt_indices,
1299            &[0],
1300            "then_concurrent() must create dependency on receipt 0"
1301        );
1302        assert_eq!(
1303            &sub2_creation.receipt_indices,
1304            &[0],
1305            "then_concurrent() must create dependency on receipt 0"
1306        );
1307    }
1308
1309    #[test]
1310    fn test_then_concurrent_split_off_then() {
1311        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1312        let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1313        let sub_account_2: AccountId = "sub2.alice.near".parse().unwrap();
1314        let sub_account_3: AccountId = "sub3.sub2.alice.near".parse().unwrap();
1315
1316        {
1317            let p1 = Promise::new(sub_account_1.clone()).create_account();
1318            let p2 = Promise::new(sub_account_2.clone()).create_account();
1319            let p3 = Promise::new(sub_account_3.clone()).create_account();
1320            Promise::new(alice())
1321                .create_account()
1322                .then_concurrent(vec![p1, p2])
1323                .split_off(1)
1324                .join()
1325                .then(p3)
1326                .detach();
1327        }
1328
1329        let receipts = get_created_receipts();
1330        let main_account_creation = &receipts[0];
1331
1332        // recursive construction switches the order of receipts
1333        let sub1_creation = &receipts[3];
1334        let sub2_creation = &receipts[1];
1335        let sub3_creation = &receipts[2];
1336
1337        // ensure we are looking at the right receipts
1338        assert_eq!(sub1_creation.receiver_id, sub_account_1);
1339        assert_eq!(sub2_creation.receiver_id, sub_account_2);
1340        assert_eq!(sub3_creation.receiver_id, sub_account_3);
1341
1342        // Find receipt index to depend on
1343        let sub2_creation_index = sub2_creation.actions[0].receipt_index().unwrap();
1344
1345        // Check dependencies were created
1346        assert!(
1347            main_account_creation.receipt_indices.is_empty(),
1348            "first receipt must not have dependencies"
1349        );
1350        assert_eq!(
1351            &sub1_creation.receipt_indices,
1352            &[0],
1353            "then_concurrent() must create dependency on receipt 0"
1354        );
1355        assert_eq!(
1356            &sub2_creation.receipt_indices,
1357            &[0],
1358            "then_concurrent() must create dependency on receipt 0"
1359        );
1360        assert_eq!(
1361            &sub3_creation.receipt_indices,
1362            &[sub2_creation_index],
1363            "then() must create dependency on sub2_creation"
1364        );
1365    }
1366
1367    #[test]
1368    fn test_then_concurrent_twice() {
1369        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1370        let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1371        let sub_account_2: AccountId = "sub2.alice.near".parse().unwrap();
1372        let sub_account_3: AccountId = "sub3.sub2.alice.near".parse().unwrap();
1373        let sub_account_4: AccountId = "sub4.sub2.alice.near".parse().unwrap();
1374
1375        {
1376            let p1 = Promise::new(sub_account_1.clone()).create_account();
1377            let p2 = Promise::new(sub_account_2.clone()).create_account();
1378            let p3 = Promise::new(sub_account_3.clone()).create_account();
1379            let p4 = Promise::new(sub_account_4.clone()).create_account();
1380            Promise::new(alice())
1381                .create_account()
1382                .then_concurrent(vec![p1, p2])
1383                .join()
1384                .then_concurrent(vec![p3, p4])
1385                .detach();
1386        }
1387
1388        let receipts = get_created_receipts();
1389        let main_account_creation = &receipts[0];
1390        // recursive construction switches the order of receipts
1391        let sub1_creation = &receipts[1];
1392        let sub2_creation = &receipts[2];
1393        let sub3_creation = &receipts[3];
1394        let sub4_creation = &receipts[4];
1395
1396        // ensure we are looking at the right receipts
1397        assert_eq!(sub1_creation.receiver_id, sub_account_1);
1398        assert_eq!(sub2_creation.receiver_id, sub_account_2);
1399        assert_eq!(sub3_creation.receiver_id, sub_account_3);
1400        assert_eq!(sub4_creation.receiver_id, sub_account_4);
1401
1402        // Find receipt indices to depend on
1403        let sub1_creation_index = sub1_creation.actions[0].receipt_index().unwrap();
1404        let sub2_creation_index = sub2_creation.actions[0].receipt_index().unwrap();
1405
1406        // Check dependencies were created
1407        assert!(
1408            main_account_creation.receipt_indices.is_empty(),
1409            "first receipt must not have dependencies"
1410        );
1411        assert_eq!(
1412            &sub1_creation.receipt_indices,
1413            &[0],
1414            "then_concurrent() must create dependency on receipt 0"
1415        );
1416        assert_eq!(
1417            &sub2_creation.receipt_indices,
1418            &[0],
1419            "then_concurrent() must create dependency on receipt 0"
1420        );
1421        assert_eq!(
1422            &sub3_creation.receipt_indices,
1423            &[sub1_creation_index, sub2_creation_index],
1424            "then_concurrent() must create dependency on sub1_creation_index + sub2_creation"
1425        );
1426        assert_eq!(
1427            &sub4_creation.receipt_indices,
1428            &[sub1_creation_index, sub2_creation_index],
1429            "then_concurrent() must create dependency on sub1_creation_index + sub2_creation"
1430        );
1431    }
1432}