near_sdk/
promise.rs

1#[cfg(feature = "abi")]
2use borsh::BorshSchema;
3use std::cell::RefCell;
4#[cfg(feature = "abi")]
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;
12use crate::CryptoHash;
13use crate::{AccountId, Gas, GasWeight, NearToken, PromiseIndex, PublicKey};
14use near_sdk_macros::near;
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
209enum PromiseSingleSubtype {
210    Regular {
211        account_id: AccountId,
212        after: RefCell<Option<Rc<Promise>>>,
213        /// Promise index that is computed only once.
214        promise_index: RefCell<Option<PromiseIndex>>,
215    },
216    Yielded(PromiseIndex),
217}
218
219struct PromiseSingle {
220    pub subtype: PromiseSingleSubtype,
221    pub actions: RefCell<Vec<PromiseAction>>,
222}
223
224impl PromiseSingle {
225    pub fn construct_recursively(&self) -> PromiseIndex {
226        let promise_index = match &self.subtype {
227            PromiseSingleSubtype::Regular { account_id, after, promise_index } => {
228                let mut promise_lock = promise_index.borrow_mut();
229                if let Some(res) = promise_lock.as_ref() {
230                    return *res;
231                }
232                let idx = if let Some(after) =
233                    after.borrow().as_deref().and_then(Promise::construct_recursively)
234                {
235                    crate::env::promise_batch_then(after, account_id)
236                } else {
237                    crate::env::promise_batch_create(account_id)
238                };
239                let actions_lock = self.actions.borrow();
240                for action in actions_lock.iter() {
241                    action.add(idx);
242                }
243                *promise_lock = Some(idx);
244                idx
245            }
246            PromiseSingleSubtype::Yielded(promise_index) => {
247                // For yielded promises, actions are added to the already-created promise
248                let actions_lock = self.actions.borrow();
249                for action in actions_lock.iter() {
250                    action.add(*promise_index);
251                }
252                *promise_index
253            }
254        };
255        promise_index
256    }
257}
258
259pub struct PromiseJoint {
260    pub promises: RefCell<VecDeque<Promise>>,
261    /// Promise index that is computed only once.
262    pub promise_index: RefCell<Option<PromiseIndex>>,
263}
264
265impl PromiseJoint {
266    pub fn construct_recursively(&self) -> Option<PromiseIndex> {
267        let mut promise_lock = self.promise_index.borrow_mut();
268        if let Some(res) = promise_lock.as_ref() {
269            return Some(*res);
270        }
271        let promises_lock = self.promises.borrow();
272        if promises_lock.is_empty() {
273            return None;
274        }
275        let res = crate::env::promise_and(
276            &promises_lock.iter().filter_map(Promise::construct_recursively).collect::<Vec<_>>(),
277        );
278        *promise_lock = Some(res);
279        Some(res)
280    }
281}
282
283/// A structure representing a result of the scheduled execution on another contract.
284///
285/// Smart contract developers will explicitly use `Promise` in two situations:
286/// * When they need to return `Promise`.
287///
288///   In the following code if someone calls method `ContractA::a` they will internally cause an
289///   execution of method `ContractB::b` of `bob_near` account, and the return value of `ContractA::a`
290///   will be what `ContractB::b` returned.
291/// ```no_run
292/// # use near_sdk::{ext_contract, near, Promise, Gas};
293/// #[ext_contract]
294/// pub trait ContractB {
295///     fn b(&mut self);
296/// }
297///
298/// #[near(contract_state)]
299/// #[derive(Default)]
300/// struct ContractA {}
301///
302/// #[near]
303/// impl ContractA {
304///     pub fn a(&self) -> Promise {
305///         contract_b::ext("bob_near".parse().unwrap()).b()
306///     }
307/// }
308/// ```
309///
310/// * When they need to create a transaction with one or many actions, e.g. the following code
311///   schedules a transaction that creates an account, transfers tokens, and assigns a public key:
312///
313/// ```no_run
314/// # use near_sdk::{Promise, env, test_utils::VMContextBuilder, testing_env, Gas, NearToken};
315/// # testing_env!(VMContextBuilder::new().signer_account_id("bob_near".parse().unwrap())
316/// #               .account_balance(NearToken::from_yoctonear(1000)).prepaid_gas(Gas::from_gas(1_000_000)).build());
317/// Promise::new("bob_near".parse().unwrap())
318///   .create_account()
319///   .transfer(NearToken::from_yoctonear(1000))
320///   .add_full_access_key(env::signer_account_pk());
321/// ```
322///
323/// More information about promises in [NEAR documentation](https://docs.near.org/build/smart-contracts/anatomy/crosscontract#promises)
324#[must_use = "return or detach explicitly via `.detach()`"]
325pub struct Promise {
326    subtype: PromiseSubtype,
327    should_return: RefCell<bool>,
328}
329
330/// Until we implement strongly typed promises we serialize them as unit struct.
331#[cfg(feature = "abi")]
332impl BorshSchema for Promise {
333    fn add_definitions_recursively(
334        definitions: &mut BTreeMap<borsh::schema::Declaration, borsh::schema::Definition>,
335    ) {
336        <()>::add_definitions_recursively(definitions);
337    }
338
339    fn declaration() -> borsh::schema::Declaration {
340        <()>::declaration()
341    }
342}
343
344#[derive(Clone)]
345enum PromiseSubtype {
346    Single(Rc<PromiseSingle>),
347    Joint(Rc<PromiseJoint>),
348}
349
350impl Promise {
351    /// Create a promise that acts on the given account.
352    /// Uses low-level [`crate::env::promise_batch_create`]
353    pub fn new(account_id: AccountId) -> Self {
354        Self::new_with_subtype(PromiseSubtype::Single(Rc::new(PromiseSingle {
355            subtype: PromiseSingleSubtype::Regular {
356                account_id,
357                after: RefCell::new(None),
358                promise_index: RefCell::new(None),
359            },
360            actions: RefCell::new(vec![]),
361        })))
362    }
363
364    const fn new_with_subtype(subtype: PromiseSubtype) -> Self {
365        Self { subtype, should_return: RefCell::new(false) }
366    }
367
368    /// Create a yielded promise that suspends execution until resumed.
369    ///
370    /// Returns a tuple of `(Promise, YieldId)` where:
371    /// - The `Promise` represents the yielded execution that will call `function_name` when resumed
372    /// - The `YieldId` can be stored and used later to resume the promise with [`YieldId::resume`]
373    ///
374    /// # Arguments
375    /// * `function_name` - The callback function to invoke when the promise is resumed
376    /// * `arguments` - Arguments to pass to the callback function
377    /// * `gas` - Base gas to allocate for the callback
378    /// * `weight` - Gas weight for distributing remaining gas
379    ///
380    /// # Important
381    /// Yielded promises have a restriction:
382    /// - **Cannot be used as continuations**: Using a yielded promise in `other.then(yielded)` will panic.
383    ///   Yielded promises must be first in the chain: `yielded.then(other)` is valid.
384    ///
385    /// # Example
386    /// ```ignore
387    /// use near_sdk::{Promise, Gas, GasWeight};
388    ///
389    /// // Create a yielded promise
390    /// let (promise, yield_id) = Promise::new_yield(
391    ///     "on_data_received",
392    ///     vec![],
393    ///     Gas::from_tgas(10),
394    ///     GasWeight(1),
395    /// );
396    ///
397    /// // Chain another promise after the yielded one (valid)
398    /// promise.then(Promise::new("other.near".parse().unwrap()).create_account());
399    ///
400    /// // Store yield_id to resume later from another transaction
401    /// ```
402    ///
403    /// Uses low-level [`crate::env::promise_yield_create`]
404    pub fn new_yield(
405        function_name: &str,
406        arguments: impl AsRef<[u8]>,
407        gas: Gas,
408        weight: GasWeight,
409    ) -> (Self, YieldId) {
410        let (promise_index, yield_id) =
411            crate::env::promise_yield_create_id(function_name, arguments, gas, weight);
412        let promise = Self::new_with_subtype(PromiseSubtype::Single(Rc::new(PromiseSingle {
413            subtype: PromiseSingleSubtype::Yielded(promise_index),
414            actions: RefCell::new(vec![]),
415        })));
416        (promise, yield_id)
417    }
418
419    fn add_action(self, action: PromiseAction) -> Self {
420        match &self.subtype {
421            PromiseSubtype::Single(x) => x.actions.borrow_mut().push(action),
422            PromiseSubtype::Joint(_) => {
423                crate::env::panic_str("Cannot add action to a joint promise.")
424            }
425        }
426        self
427    }
428
429    /// Create account on which this promise acts.
430    /// Uses low-level [`crate::env::promise_batch_action_create_account`]
431    pub fn create_account(self) -> Self {
432        self.add_action(PromiseAction::CreateAccount)
433    }
434
435    /// Deploy a smart contract to the account on which this promise acts.
436    /// Uses low-level [`crate::env::promise_batch_action_deploy_contract`]
437    pub fn deploy_contract(self, code: impl Into<Vec<u8>>) -> Self {
438        self.add_action(PromiseAction::DeployContract { code: code.into() })
439    }
440
441    #[cfg(feature = "global-contracts")]
442    /// Deploy a global smart contract using the provided contract code.
443    /// Uses low-level [`crate::env::promise_batch_action_deploy_global_contract`]
444    ///
445    /// # Examples
446    /// ```no_run
447    /// use near_sdk::{Promise, NearToken};
448    ///
449    /// let code = vec![0u8; 100]; // Contract bytecode
450    /// Promise::new("alice.near".parse().unwrap())
451    ///     .create_account()
452    ///     .transfer(NearToken::from_yoctonear(1000))
453    ///     .deploy_global_contract(code);
454    /// ```
455    pub fn deploy_global_contract(self, code: impl Into<Vec<u8>>) -> Self {
456        self.add_action(PromiseAction::DeployGlobalContract { code: code.into() })
457    }
458
459    #[cfg(feature = "global-contracts")]
460    /// Deploy a global smart contract, identifiable by the predecessor's account ID.
461    /// Uses low-level [`crate::env::promise_batch_action_deploy_global_contract_by_account_id`]
462    ///
463    /// # Examples
464    /// ```no_run
465    /// use near_sdk::{Promise, NearToken};
466    ///
467    /// let code = vec![0u8; 100]; // Contract bytecode
468    /// Promise::new("alice.near".parse().unwrap())
469    ///     .create_account()
470    ///     .transfer(NearToken::from_yoctonear(1000))
471    ///     .deploy_global_contract_by_account_id(code);
472    /// ```
473    pub fn deploy_global_contract_by_account_id(self, code: impl Into<Vec<u8>>) -> Self {
474        self.add_action(PromiseAction::DeployGlobalContractByAccountId { code: code.into() })
475    }
476
477    #[cfg(feature = "global-contracts")]
478    /// Use an existing global contract by code hash.
479    /// Uses low-level [`crate::env::promise_batch_action_use_global_contract`]
480    ///
481    /// # Examples
482    /// ```no_run
483    /// use near_sdk::{Promise, NearToken};
484    ///
485    /// let code_hash = [0u8; 32]; // 32-byte hash (CryptoHash)
486    /// Promise::new("alice.near".parse().unwrap())
487    ///     .create_account()
488    ///     .transfer(NearToken::from_yoctonear(1000))
489    ///     .use_global_contract(code_hash);
490    /// ```
491    pub fn use_global_contract(self, code_hash: impl Into<CryptoHash>) -> Self {
492        self.add_action(PromiseAction::UseGlobalContract { code_hash: code_hash.into() })
493    }
494
495    #[cfg(feature = "global-contracts")]
496    /// Use an existing global contract by referencing the account that deployed it.
497    /// Uses low-level [`crate::env::promise_batch_action_use_global_contract_by_account_id`]
498    ///
499    /// # Examples
500    /// ```no_run
501    /// use near_sdk::{Promise, NearToken, AccountId};
502    ///
503    /// Promise::new("alice.near".parse().unwrap())
504    ///     .create_account()
505    ///     .transfer(NearToken::from_yoctonear(1000))
506    ///     .use_global_contract_by_account_id("deployer.near".parse().unwrap());
507    /// ```
508    pub fn use_global_contract_by_account_id(self, account_id: AccountId) -> Self {
509        self.add_action(PromiseAction::UseGlobalContractByAccountId { account_id })
510    }
511
512    /// Creates a deterministic account with the given code, deposit, and data.
513    #[cfg(feature = "deterministic-account-ids")]
514    pub fn state_init(self, state_init: crate::state_init::StateInit, deposit: NearToken) -> Self {
515        self.add_action(PromiseAction::DeterministicStateInit { state_init, deposit })
516    }
517
518    /// A low-level interface for making a function call to the account that this promise acts on.
519    /// Uses low-level [`crate::env::promise_batch_action_function_call`]
520    pub fn function_call(
521        self,
522        function_name: impl Into<String>,
523        arguments: impl Into<Vec<u8>>,
524        amount: NearToken,
525        gas: Gas,
526    ) -> Self {
527        self.add_action(PromiseAction::FunctionCall {
528            function_name: function_name.into(),
529            arguments: arguments.into(),
530            amount,
531            gas,
532        })
533    }
534
535    /// A low-level interface for making a function call to the account that this promise acts on.
536    /// unlike [`Promise::function_call`], this function accepts a weight to use relative unused gas
537    /// on this function call at the end of the scheduling method execution.
538    /// Uses low-level [`crate::env::promise_batch_action_function_call_weight`]
539    pub fn function_call_weight(
540        self,
541        function_name: impl Into<String>,
542        arguments: impl Into<Vec<u8>>,
543        amount: NearToken,
544        gas: Gas,
545        weight: GasWeight,
546    ) -> Self {
547        self.add_action(PromiseAction::FunctionCallWeight {
548            function_name: function_name.into(),
549            arguments: arguments.into(),
550            amount,
551            gas,
552            weight,
553        })
554    }
555
556    /// Transfer tokens to the account that this promise acts on.
557    /// Uses low-level [`crate::env::promise_batch_action_transfer`]
558    pub fn transfer(self, amount: NearToken) -> Self {
559        self.add_action(PromiseAction::Transfer { amount })
560    }
561
562    /// Stake the account for the given amount of tokens using the given public key.
563    /// Uses low-level [`crate::env::promise_batch_action_stake`]
564    pub fn stake(self, amount: NearToken, public_key: PublicKey) -> Self {
565        self.add_action(PromiseAction::Stake { amount, public_key })
566    }
567
568    /// Add full access key to the given account.
569    /// Uses low-level [`crate::env::promise_batch_action_add_key_with_full_access`]
570    pub fn add_full_access_key(self, public_key: PublicKey) -> Self {
571        self.add_full_access_key_with_nonce(public_key, 0)
572    }
573
574    /// Add full access key to the given account with a provided nonce.
575    /// Uses low-level [`crate::env::promise_batch_action_add_key_with_full_access`]
576    pub fn add_full_access_key_with_nonce(self, public_key: PublicKey, nonce: u64) -> Self {
577        self.add_action(PromiseAction::AddFullAccessKey { public_key, nonce })
578    }
579
580    /// Add an access key that is restricted to only calling a smart contract on some account using
581    /// only a restricted set of methods. Here `function_names` is a comma separated list of methods,
582    /// e.g. `"method_a,method_b"`.
583    /// Uses low-level [`crate::env::promise_batch_action_add_key_allowance_with_function_call`]
584    pub fn add_access_key_allowance(
585        self,
586        public_key: PublicKey,
587        allowance: Allowance,
588        receiver_id: AccountId,
589        function_names: impl Into<String>,
590    ) -> Self {
591        self.add_access_key_allowance_with_nonce(
592            public_key,
593            allowance,
594            receiver_id,
595            function_names,
596            0,
597        )
598    }
599
600    #[deprecated(since = "5.0.0", note = "Use add_access_key_allowance instead")]
601    pub fn add_access_key(
602        self,
603        public_key: PublicKey,
604        allowance: NearToken,
605        receiver_id: AccountId,
606        function_names: impl Into<String>,
607    ) -> Self {
608        let allowance = migrate_to_allowance(allowance);
609        self.add_access_key_allowance(public_key, allowance, receiver_id, function_names)
610    }
611
612    /// Add an access key with a provided nonce.
613    /// Uses low-level [`crate::env::promise_batch_action_add_key_allowance_with_function_call`]
614    pub fn add_access_key_allowance_with_nonce(
615        self,
616        public_key: PublicKey,
617        allowance: Allowance,
618        receiver_id: AccountId,
619        function_names: impl Into<String>,
620        nonce: u64,
621    ) -> Self {
622        self.add_action(PromiseAction::AddAccessKey {
623            public_key,
624            allowance,
625            receiver_id,
626            function_names: function_names.into(),
627            nonce,
628        })
629    }
630
631    #[deprecated(since = "5.0.0", note = "Use add_access_key_allowance_with_nonce instead")]
632    pub fn add_access_key_with_nonce(
633        self,
634        public_key: PublicKey,
635        allowance: NearToken,
636        receiver_id: AccountId,
637        function_names: impl Into<String>,
638        nonce: u64,
639    ) -> Self {
640        let allowance = migrate_to_allowance(allowance);
641        self.add_access_key_allowance_with_nonce(
642            public_key,
643            allowance,
644            receiver_id,
645            function_names,
646            nonce,
647        )
648    }
649
650    /// Delete access key from the given account.
651    /// Uses low-level [`crate::env::promise_batch_action_delete_key`]
652    pub fn delete_key(self, public_key: PublicKey) -> Self {
653        self.add_action(PromiseAction::DeleteKey { public_key })
654    }
655
656    /// Delete the given account.
657    /// Uses low-level [`crate::env::promise_batch_action_delete_account`]
658    pub fn delete_account(self, beneficiary_id: AccountId) -> Self {
659        self.add_action(PromiseAction::DeleteAccount { beneficiary_id })
660    }
661
662    /// Merge this promise with another promise, so that we can schedule execution of another
663    /// smart contract right after all merged promises finish.
664    ///
665    /// Note, once the promises are merged it is not possible to add actions to them, e.g. the
666    /// following code will panic during the execution of the smart contract:
667    ///
668    /// ```no_run
669    /// # use near_sdk::{Promise, testing_env};
670    /// let p1 = Promise::new("bob_near".parse().unwrap()).create_account();
671    /// let p2 = Promise::new("carol_near".parse().unwrap()).create_account();
672    /// let p3 = p1.and(p2);
673    /// // p3.create_account();
674    /// ```
675    /// Uses low-level [`crate::env::promise_and`]
676    pub fn and(self, other: Promise) -> Promise {
677        match (&self.subtype, &other.subtype) {
678            (PromiseSubtype::Joint(x), PromiseSubtype::Joint(o)) => {
679                x.promises.borrow_mut().append(&mut o.promises.borrow_mut());
680                self
681            }
682            (PromiseSubtype::Joint(x), _) => {
683                x.promises.borrow_mut().push_back(other);
684                self
685            }
686            (_, PromiseSubtype::Joint(o)) => {
687                o.promises.borrow_mut().push_front(self);
688                other
689            }
690            _ => Promise {
691                subtype: PromiseSubtype::Joint(Rc::new(PromiseJoint {
692                    promises: RefCell::new([self, other].into()),
693                    promise_index: RefCell::new(None),
694                })),
695                should_return: RefCell::new(false),
696            },
697        }
698    }
699
700    /// Schedules execution of another promise right after the current promise finish executing.
701    ///
702    /// In the following code `bob_near` and `dave_near` will be created concurrently. `carol_near`
703    /// creation will wait for `bob_near` to be created, and `eva_near` will wait for both `carol_near`
704    /// and `dave_near` to be created first.
705    /// ```no_run
706    /// # use near_sdk::{Promise, VMContext, testing_env};
707    /// let p1 = Promise::new("bob_near".parse().unwrap()).create_account();
708    /// let p2 = Promise::new("carol_near".parse().unwrap()).create_account();
709    /// let p3 = Promise::new("dave_near".parse().unwrap()).create_account();
710    /// let p4 = Promise::new("eva_near".parse().unwrap()).create_account();
711    /// p1.then(p2).and(p3).then(p4);
712    /// ```
713    /// Uses low-level [`crate::env::promise_batch_then`]
714    pub fn then(self, other: Promise) -> Promise {
715        Rc::new(self).then_impl(other)
716    }
717
718    /// Shared, private implementation between `then` and `then_concurrent`.
719    ///
720    /// This takes self as a reference counted object promise, to allow multiple
721    /// dependencies pointing to the same promise without duplicating that
722    /// promise.
723    fn then_impl(self: Rc<Self>, other: Promise) -> Promise {
724        match &other.subtype {
725            PromiseSubtype::Single(x) => match &x.subtype {
726                PromiseSingleSubtype::Regular { after, .. } => {
727                    let mut after = after.borrow_mut();
728                    if after.is_some() {
729                        crate::env::panic_str(
730                            "Cannot callback promise which is already scheduled after another",
731                        );
732                    }
733                    *after = Some(self)
734                }
735                PromiseSingleSubtype::Yielded(_) => {
736                    crate::env::panic_str("Cannot callback yielded promise.")
737                }
738            },
739            PromiseSubtype::Joint(_) => crate::env::panic_str("Cannot callback joint promise."),
740        }
741        other
742    }
743
744    /// Schedules execution of multiple concurrent promises right after the
745    /// current promise finishes executing.
746    ///
747    /// This method will send the same return value as a data receipt to all
748    /// following receipts.
749    ///
750    /// In the following code, `bob_near` is created first, `carol_near` second,
751    /// and finally `dave_near` and `eva_near` are created concurrently.
752    ///
753    /// ```no_run
754    /// # use near_sdk::Promise;
755    /// let p1 = Promise::new("bob_near".parse().unwrap()).create_account();
756    /// let p2 = Promise::new("carol_near".parse().unwrap()).create_account();
757    /// let p3 = Promise::new("dave_near".parse().unwrap()).create_account();
758    /// let p4 = Promise::new("eva_near".parse().unwrap()).create_account();
759    /// p1.then(p2).then_concurrent(vec![p3, p4]);
760    /// ```
761    ///
762    /// The returned [`ConcurrentPromises`] allows chaining more promises.
763    ///
764    /// In the following code, `bob_near` is created first, next `carol_near`
765    /// and `dave_near` are created concurrently, and finally `eva_near` is
766    /// created after all others have been created.
767    ///
768    /// ```no_run
769    /// # use near_sdk::Promise;
770    /// let p1 = Promise::new("bob_near".parse().unwrap()).create_account();
771    /// let p2 = Promise::new("carol_near".parse().unwrap()).create_account();
772    /// let p3 = Promise::new("dave_near".parse().unwrap()).create_account();
773    /// let p4 = Promise::new("eva_near".parse().unwrap()).create_account();
774    /// p1.then_concurrent(vec![p2, p3]).join().then(p4);
775    /// ```
776    pub fn then_concurrent(
777        self,
778        promises: impl IntoIterator<Item = Promise>,
779    ) -> ConcurrentPromises {
780        let this = Rc::new(self);
781        let mapped_promises =
782            promises.into_iter().map(|other| Rc::clone(&this).then_impl(other)).collect();
783        ConcurrentPromises { promises: mapped_promises }
784    }
785
786    /// A specialized, relatively low-level API method. Allows to mark the given promise as the one
787    /// that should be considered as a return value.
788    ///
789    /// In the below code `a1` and `a2` functions are equivalent.
790    /// ```
791    /// # use near_sdk::{ext_contract, Gas, near, Promise};
792    /// #[ext_contract]
793    /// pub trait ContractB {
794    ///     fn b(&mut self);
795    /// }
796    ///
797    /// #[near(contract_state)]
798    /// #[derive(Default)]
799    /// struct ContractA {}
800    ///
801    /// #[near]
802    /// impl ContractA {
803    ///     pub fn a1(&self) {
804    ///        contract_b::ext("bob_near".parse().unwrap()).b().as_return();
805    ///     }
806    ///
807    ///     pub fn a2(&self) -> Promise {
808    ///        contract_b::ext("bob_near".parse().unwrap()).b()
809    ///     }
810    /// }
811    /// ```
812    /// Makes the promise to use low-level [`crate::env::promise_return`].
813    #[allow(clippy::wrong_self_convention)]
814    pub fn as_return(self) -> Self {
815        *self.should_return.borrow_mut() = true;
816        self
817    }
818
819    fn construct_recursively(&self) -> Option<PromiseIndex> {
820        let res = match &self.subtype {
821            PromiseSubtype::Single(x) => x.construct_recursively(),
822            PromiseSubtype::Joint(x) => x.construct_recursively()?,
823        };
824        if *self.should_return.borrow() {
825            crate::env::promise_return(res);
826        }
827        Some(res)
828    }
829
830    /// Explicitly detach given promise
831    #[inline]
832    pub fn detach(self) {}
833}
834
835impl Drop for Promise {
836    fn drop(&mut self) {
837        self.construct_recursively();
838    }
839}
840
841impl serde::Serialize for Promise {
842    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
843    where
844        S: serde::Serializer,
845    {
846        *self.should_return.borrow_mut() = true;
847        serializer.serialize_unit()
848    }
849}
850
851impl borsh::BorshSerialize for Promise {
852    fn serialize<W: Write>(&self, _writer: &mut W) -> Result<(), Error> {
853        *self.should_return.borrow_mut() = true;
854
855        // Intentionally no bytes written for the promise, the return value from the promise
856        // will be considered as the return value from the contract call.
857        Ok(())
858    }
859}
860
861#[cfg(feature = "abi")]
862impl schemars::JsonSchema for Promise {
863    fn schema_name() -> String {
864        "Promise".to_string()
865    }
866
867    fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
868        // Since promises are untyped, for now we represent Promise results with the schema
869        // `true` which matches everything (i.e. always passes validation)
870        schemars::schema::Schema::Bool(true)
871    }
872}
873
874/// A unique identifier for a yielded promise that can be used to resume execution.
875///
876/// `YieldId` is returned by [`Promise::new_yield`] and can be stored or passed to another
877/// transaction to resume the yielded promise with data.
878///
879/// # Example
880/// ```ignore
881/// // In the first transaction, create a yielded promise
882/// let (promise, yield_id) = Promise::new_yield(
883///     "on_resume",
884///     vec![],
885///     Gas::from_tgas(5),
886///     GasWeight(1),
887/// );
888/// // Store yield_id somewhere (e.g., contract state)
889///
890/// // In a later transaction, resume with data
891/// yield_id.resume(b"result data");
892/// ```
893#[near(inside_nearsdk, serializers = [json, borsh])]
894#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
895pub struct YieldId(
896    #[serde_as(as = "::serde_with::base64::Base64")]
897    #[cfg_attr(feature = "abi", schemars(with = "String"))]
898    pub(crate) CryptoHash,
899);
900
901/// Error returned when attempting to resume a yielded promise that was not found.
902///
903/// This occurs when:
904/// - The promise was already resumed
905/// - The promise timed out
906/// - The `YieldId` is invalid
907#[derive(Debug, Copy, Clone, Eq, PartialEq)]
908pub struct ResumeError;
909
910impl std::fmt::Display for ResumeError {
911    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
912        write!(f, "failed to resume yielded promise: not found or already resumed")
913    }
914}
915
916impl std::error::Error for ResumeError {}
917
918impl YieldId {
919    /// Resume the yielded promise with the provided data.
920    ///
921    /// Returns `Ok(())` if the promise was successfully resumed, or `Err(ResumeError)` if no promise
922    /// with this `YieldId` was found (e.g., if it was already resumed or timed out).
923    ///
924    /// Uses low-level [`crate::env::promise_yield_resume`]
925    pub fn resume(self, data: impl AsRef<[u8]>) -> Result<(), ResumeError> {
926        if crate::env::promise_yield_resume(&self.0, data) {
927            Ok(())
928        } else {
929            Err(ResumeError)
930        }
931    }
932}
933
934/// When the method can return either a promise or a value, it can be called with `PromiseOrValue::Promise`
935/// or `PromiseOrValue::Value` to specify which one should be returned.
936/// # Example
937/// ```no_run
938/// # use near_sdk::{ext_contract, near, Gas, PromiseOrValue};
939/// #[ext_contract]
940/// pub trait ContractA {
941///     fn a(&mut self);
942/// }
943///
944/// let value = Some(true);
945/// let val: PromiseOrValue<bool> = if let Some(value) = value {
946///     PromiseOrValue::Value(value)
947/// } else {
948///     contract_a::ext("bob_near".parse().unwrap()).a().into()
949/// };
950/// ```
951#[must_use = "return or detach explicitly via `.detach()`"]
952#[derive(serde::Serialize)]
953#[serde(untagged)]
954pub enum PromiseOrValue<T> {
955    Promise(Promise),
956    Value(T),
957}
958
959impl<T> PromiseOrValue<T> {
960    /// Explicitly detach if it was a promise
961    #[inline]
962    pub fn detach(self) {}
963}
964
965#[cfg(feature = "abi")]
966impl<T> BorshSchema for PromiseOrValue<T>
967where
968    T: BorshSchema,
969{
970    fn add_definitions_recursively(
971        definitions: &mut BTreeMap<borsh::schema::Declaration, borsh::schema::Definition>,
972    ) {
973        T::add_definitions_recursively(definitions);
974    }
975
976    fn declaration() -> borsh::schema::Declaration {
977        T::declaration()
978    }
979}
980
981impl<T> From<Promise> for PromiseOrValue<T> {
982    fn from(promise: Promise) -> Self {
983        PromiseOrValue::Promise(promise)
984    }
985}
986
987impl<T: borsh::BorshSerialize> borsh::BorshSerialize for PromiseOrValue<T> {
988    fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
989        match self {
990            // Only actual value is serialized.
991            PromiseOrValue::Value(x) => x.serialize(writer),
992            // The promise is dropped to cause env::promise calls.
993            PromiseOrValue::Promise(p) => p.serialize(writer),
994        }
995    }
996}
997
998#[cfg(feature = "abi")]
999impl<T: schemars::JsonSchema> schemars::JsonSchema for PromiseOrValue<T> {
1000    fn schema_name() -> String {
1001        format!("PromiseOrValue{}", T::schema_name())
1002    }
1003
1004    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1005        T::json_schema(gen)
1006    }
1007}
1008
1009/// A list of promises that are executed concurrently.
1010///
1011/// This is the return type of [`Promise::then_concurrent`] and it wraps the
1012/// contained promises for a more convenient syntax when chaining calls.
1013///
1014/// Use [`ConcurrentPromises::join`] to create a new promises that waits for all
1015/// concurrent promises and takes all their return values as inputs.
1016///
1017/// Use [`ConcurrentPromises::split_off`] to divide the list of promises into
1018/// subgroups that can be joined independently.
1019#[must_use = "return or detach explicitly via `.detach()`"]
1020pub struct ConcurrentPromises {
1021    promises: Vec<Promise>,
1022}
1023
1024impl ConcurrentPromises {
1025    /// Create a new promises that waits for all contained concurrent promises.
1026    ///
1027    /// The returned promise is a [`Promise::and`] combination of all contained
1028    /// promises. Chain it with a [`Promise::then`] to wait for them to finish
1029    /// and receive all their outputs as inputs in a following function call.
1030    pub fn join(self) -> Promise {
1031        self.promises
1032            .into_iter()
1033            .reduce(|left, right| left.and(right))
1034            .expect("cannot join empty concurrent promises")
1035    }
1036
1037    /// Splits the contained list of promises into two at the given index,
1038    /// returning a new [`ConcurrentPromises`] containing the elements in the
1039    /// range [at, len).
1040    ///
1041    /// After the call, the original [`ConcurrentPromises`] will be left
1042    /// containing the elements [0, at).
1043    pub fn split_off(&mut self, at: usize) -> ConcurrentPromises {
1044        let right_side = self.promises.split_off(at);
1045        ConcurrentPromises { promises: right_side }
1046    }
1047
1048    /// Explicitly detach given promises
1049    #[inline]
1050    pub fn detach(self) {}
1051}
1052
1053#[cfg(not(target_arch = "wasm32"))]
1054#[cfg(test)]
1055mod tests {
1056    use crate::mock::MockAction;
1057    use crate::test_utils::get_created_receipts;
1058    use crate::test_utils::test_env::{alice, bob};
1059    use crate::{
1060        test_utils::VMContextBuilder, testing_env, AccountId, Allowance, NearToken, Promise,
1061        PublicKey,
1062    };
1063
1064    fn pk() -> PublicKey {
1065        "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp".parse().unwrap()
1066    }
1067
1068    fn get_actions() -> std::vec::IntoIter<MockAction> {
1069        let receipts = get_created_receipts();
1070        let first_receipt = receipts.into_iter().next().unwrap();
1071        first_receipt.actions.into_iter()
1072    }
1073
1074    fn has_add_key_with_full_access(public_key: PublicKey, nonce: Option<u64>) -> bool {
1075        let public_key = near_crypto::PublicKey::try_from(public_key).unwrap();
1076        get_actions().any(|el| {
1077            matches!(
1078                el,
1079                MockAction::AddKeyWithFullAccess { public_key: p, nonce: n, receipt_index: _, }
1080                if p == public_key
1081                    && (nonce.is_none() || Some(n) == nonce)
1082            )
1083        })
1084    }
1085
1086    fn has_add_key_with_function_call(
1087        public_key: PublicKey,
1088        allowance: u128,
1089        receiver_id: AccountId,
1090        function_names: String,
1091        nonce: Option<u64>,
1092    ) -> bool {
1093        let public_key = near_crypto::PublicKey::try_from(public_key).unwrap();
1094        get_actions().any(|el| {
1095            matches!(
1096                el,
1097                MockAction::AddKeyWithFunctionCall {
1098                    public_key: p,
1099                    allowance: a,
1100                    receiver_id: r,
1101                    method_names,
1102                    nonce: n,
1103                    receipt_index: _,
1104                }
1105                if p == public_key
1106                    && a.unwrap() == NearToken::from_yoctonear(allowance)
1107                    && r == receiver_id
1108                    && method_names.clone() == function_names.split(',').collect::<Vec<_>>()
1109                    && (nonce.is_none() || Some(n) == nonce)
1110            )
1111        })
1112    }
1113
1114    #[test]
1115    fn test_add_full_access_key() {
1116        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1117
1118        let public_key: PublicKey = pk();
1119
1120        // Promise is only executed when dropped so we put it in its own scope to make sure receipts
1121        // are ready afterwards.
1122        {
1123            Promise::new(alice()).create_account().add_full_access_key(public_key.clone()).detach();
1124        }
1125
1126        assert!(has_add_key_with_full_access(public_key, None));
1127    }
1128
1129    #[test]
1130    fn test_add_full_access_key_with_nonce() {
1131        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1132
1133        let public_key: PublicKey = pk();
1134        let nonce = 42;
1135
1136        {
1137            Promise::new(alice())
1138                .create_account()
1139                .add_full_access_key_with_nonce(public_key.clone(), nonce)
1140                .detach();
1141        }
1142
1143        assert!(has_add_key_with_full_access(public_key, Some(nonce)));
1144    }
1145
1146    #[test]
1147    fn test_add_access_key_allowance() {
1148        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1149
1150        let public_key: PublicKey = pk();
1151        let allowance = 100;
1152        let receiver_id = bob();
1153        let function_names = "method_a,method_b".to_string();
1154
1155        {
1156            Promise::new(alice())
1157                .create_account()
1158                .add_access_key_allowance(
1159                    public_key.clone(),
1160                    Allowance::Limited(allowance.try_into().unwrap()),
1161                    receiver_id.clone(),
1162                    function_names.clone(),
1163                )
1164                .detach();
1165        }
1166
1167        assert!(has_add_key_with_function_call(
1168            public_key,
1169            allowance,
1170            receiver_id,
1171            function_names,
1172            None
1173        ));
1174    }
1175
1176    #[test]
1177    fn test_add_access_key() {
1178        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1179
1180        let public_key: PublicKey = pk();
1181        let allowance = NearToken::from_yoctonear(100);
1182        let receiver_id = bob();
1183        let function_names = "method_a,method_b".to_string();
1184
1185        {
1186            #[allow(deprecated)]
1187            Promise::new(alice())
1188                .create_account()
1189                .add_access_key(
1190                    public_key.clone(),
1191                    allowance,
1192                    receiver_id.clone(),
1193                    function_names.clone(),
1194                )
1195                .detach();
1196        }
1197
1198        assert!(has_add_key_with_function_call(
1199            public_key,
1200            allowance.as_yoctonear(),
1201            receiver_id,
1202            function_names,
1203            None
1204        ));
1205    }
1206
1207    #[test]
1208    fn test_add_access_key_allowance_with_nonce() {
1209        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1210
1211        let public_key: PublicKey = pk();
1212        let allowance = 100;
1213        let receiver_id = bob();
1214        let function_names = "method_a,method_b".to_string();
1215        let nonce = 42;
1216
1217        {
1218            Promise::new(alice())
1219                .create_account()
1220                .add_access_key_allowance_with_nonce(
1221                    public_key.clone(),
1222                    Allowance::Limited(allowance.try_into().unwrap()),
1223                    receiver_id.clone(),
1224                    function_names.clone(),
1225                    nonce,
1226                )
1227                .detach();
1228        }
1229
1230        assert!(has_add_key_with_function_call(
1231            public_key,
1232            allowance,
1233            receiver_id,
1234            function_names,
1235            Some(nonce)
1236        ));
1237    }
1238
1239    #[test]
1240    fn test_add_access_key_with_nonce() {
1241        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1242
1243        let public_key: PublicKey = pk();
1244        let allowance = NearToken::from_yoctonear(100);
1245        let receiver_id = bob();
1246        let function_names = "method_a,method_b".to_string();
1247        let nonce = 42;
1248
1249        {
1250            #[allow(deprecated)]
1251            Promise::new(alice())
1252                .create_account()
1253                .add_access_key_with_nonce(
1254                    public_key.clone(),
1255                    allowance,
1256                    receiver_id.clone(),
1257                    function_names.clone(),
1258                    nonce,
1259                )
1260                .detach();
1261        }
1262
1263        assert!(has_add_key_with_function_call(
1264            public_key,
1265            allowance.as_yoctonear(),
1266            receiver_id,
1267            function_names,
1268            Some(nonce)
1269        ));
1270    }
1271
1272    #[test]
1273    fn test_delete_key() {
1274        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1275
1276        let public_key: PublicKey = pk();
1277
1278        {
1279            Promise::new(alice())
1280                .create_account()
1281                .add_full_access_key(public_key.clone())
1282                .delete_key(public_key.clone())
1283                .detach();
1284        }
1285        let public_key = near_crypto::PublicKey::try_from(public_key).unwrap();
1286
1287        let has_action = get_actions().any(|el| {
1288            matches!(
1289                el,
1290                MockAction::DeleteKey { public_key: p , receipt_index: _, } if p == public_key
1291            )
1292        });
1293        assert!(has_action);
1294    }
1295
1296    #[cfg(feature = "global-contracts")]
1297    #[test]
1298    fn test_deploy_global_contract() {
1299        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1300
1301        let code = vec![1, 2, 3, 4];
1302
1303        {
1304            Promise::new(alice()).create_account().deploy_global_contract(code.clone()).detach();
1305        }
1306
1307        let has_action = get_actions().any(|el| {
1308            matches!(
1309                el,
1310                MockAction::DeployGlobalContract { code: c, receipt_index: _, mode: _ } if c == code
1311            )
1312        });
1313        assert!(has_action);
1314    }
1315
1316    #[cfg(feature = "global-contracts")]
1317    #[test]
1318    fn test_deploy_global_contract_by_account_id() {
1319        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1320
1321        let code = vec![5, 6, 7, 8];
1322
1323        {
1324            Promise::new(alice())
1325                .create_account()
1326                .deploy_global_contract_by_account_id(code.clone())
1327                .detach();
1328        }
1329
1330        let has_action = get_actions().any(|el| {
1331            matches!(
1332                el,
1333                MockAction::DeployGlobalContract { code: c, receipt_index: _, mode: _ } if c == code
1334            )
1335        });
1336        assert!(has_action);
1337    }
1338
1339    #[cfg(feature = "global-contracts")]
1340    #[test]
1341    fn test_use_global_contract() {
1342        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1343
1344        let code_hash = [0u8; 32];
1345
1346        {
1347            Promise::new(alice()).create_account().use_global_contract(code_hash).detach();
1348        }
1349
1350        // Check if any UseGlobalContract action exists
1351        let has_action = get_actions().any(|el| matches!(el, MockAction::UseGlobalContract { .. }));
1352        assert!(has_action);
1353    }
1354
1355    #[cfg(feature = "global-contracts")]
1356    #[test]
1357    fn test_use_global_contract_by_account_id() {
1358        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1359
1360        let deployer = bob();
1361
1362        {
1363            Promise::new(alice())
1364                .create_account()
1365                .use_global_contract_by_account_id(deployer.clone())
1366                .detach();
1367        }
1368
1369        let has_action = get_actions().any(|el| {
1370            matches!(
1371                el,
1372                MockAction::UseGlobalContract {
1373                    contract_id: near_primitives::action::GlobalContractIdentifier::AccountId(contract_id),
1374                    receipt_index: _
1375                }
1376                if contract_id == deployer
1377            )
1378        });
1379        assert!(has_action);
1380    }
1381
1382    #[test]
1383    fn test_then() {
1384        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1385        let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1386
1387        {
1388            Promise::new(alice())
1389                .create_account()
1390                .then(Promise::new(sub_account_1).create_account())
1391                .detach();
1392        }
1393
1394        let receipts = get_created_receipts();
1395        let main_account_creation = &receipts[0];
1396        let sub_creation = &receipts[1];
1397
1398        assert!(
1399            main_account_creation.receipt_indices.is_empty(),
1400            "first receipt must not have dependencies"
1401        );
1402        assert_eq!(
1403            &sub_creation.receipt_indices,
1404            &[0],
1405            "then_concurrent() must create dependency on receipt 0"
1406        );
1407    }
1408
1409    #[test]
1410    fn test_then_concurrent() {
1411        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1412        let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1413        let sub_account_2: AccountId = "sub2.alice.near".parse().unwrap();
1414
1415        {
1416            let p1 = Promise::new(sub_account_1.clone()).create_account();
1417            let p2 = Promise::new(sub_account_2.clone()).create_account();
1418            Promise::new(alice()).create_account().then_concurrent(vec![p1, p2]).detach();
1419        }
1420
1421        let receipts = get_created_receipts();
1422        let main_account_creation = &receipts[0];
1423
1424        let sub1_creation = &receipts[1];
1425        let sub2_creation = &receipts[2];
1426
1427        // ensure we are looking at the right receipts
1428        assert_eq!(sub1_creation.receiver_id, sub_account_1);
1429        assert_eq!(sub2_creation.receiver_id, sub_account_2);
1430
1431        // Check dependencies were created
1432        assert!(
1433            main_account_creation.receipt_indices.is_empty(),
1434            "first receipt must not have dependencies"
1435        );
1436        assert_eq!(
1437            &sub1_creation.receipt_indices,
1438            &[0],
1439            "then_concurrent() must create dependency on receipt 0"
1440        );
1441        assert_eq!(
1442            &sub2_creation.receipt_indices,
1443            &[0],
1444            "then_concurrent() must create dependency on receipt 0"
1445        );
1446    }
1447
1448    #[test]
1449    fn test_then_concurrent_split_off_then() {
1450        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1451        let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1452        let sub_account_2: AccountId = "sub2.alice.near".parse().unwrap();
1453        let sub_account_3: AccountId = "sub3.sub2.alice.near".parse().unwrap();
1454
1455        {
1456            let p1 = Promise::new(sub_account_1.clone()).create_account();
1457            let p2 = Promise::new(sub_account_2.clone()).create_account();
1458            let p3 = Promise::new(sub_account_3.clone()).create_account();
1459            Promise::new(alice())
1460                .create_account()
1461                .then_concurrent(vec![p1, p2])
1462                .split_off(1)
1463                .join()
1464                .then(p3)
1465                .detach();
1466        }
1467
1468        let receipts = get_created_receipts();
1469        let main_account_creation = &receipts[0];
1470
1471        // recursive construction switches the order of receipts
1472        let sub1_creation = &receipts[3];
1473        let sub2_creation = &receipts[1];
1474        let sub3_creation = &receipts[2];
1475
1476        // ensure we are looking at the right receipts
1477        assert_eq!(sub1_creation.receiver_id, sub_account_1);
1478        assert_eq!(sub2_creation.receiver_id, sub_account_2);
1479        assert_eq!(sub3_creation.receiver_id, sub_account_3);
1480
1481        // Find receipt index to depend on
1482        let sub2_creation_index = sub2_creation.actions[0].receipt_index().unwrap();
1483
1484        // Check dependencies were created
1485        assert!(
1486            main_account_creation.receipt_indices.is_empty(),
1487            "first receipt must not have dependencies"
1488        );
1489        assert_eq!(
1490            &sub1_creation.receipt_indices,
1491            &[0],
1492            "then_concurrent() must create dependency on receipt 0"
1493        );
1494        assert_eq!(
1495            &sub2_creation.receipt_indices,
1496            &[0],
1497            "then_concurrent() must create dependency on receipt 0"
1498        );
1499        assert_eq!(
1500            &sub3_creation.receipt_indices,
1501            &[sub2_creation_index],
1502            "then() must create dependency on sub2_creation"
1503        );
1504    }
1505
1506    #[test]
1507    fn test_then_concurrent_twice() {
1508        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1509        let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1510        let sub_account_2: AccountId = "sub2.alice.near".parse().unwrap();
1511        let sub_account_3: AccountId = "sub3.sub2.alice.near".parse().unwrap();
1512        let sub_account_4: AccountId = "sub4.sub2.alice.near".parse().unwrap();
1513
1514        {
1515            let p1 = Promise::new(sub_account_1.clone()).create_account();
1516            let p2 = Promise::new(sub_account_2.clone()).create_account();
1517            let p3 = Promise::new(sub_account_3.clone()).create_account();
1518            let p4 = Promise::new(sub_account_4.clone()).create_account();
1519            Promise::new(alice())
1520                .create_account()
1521                .then_concurrent(vec![p1, p2])
1522                .join()
1523                .then_concurrent(vec![p3, p4])
1524                .detach();
1525        }
1526
1527        let receipts = get_created_receipts();
1528        let main_account_creation = &receipts[0];
1529        // recursive construction switches the order of receipts
1530        let sub1_creation = &receipts[1];
1531        let sub2_creation = &receipts[2];
1532        let sub3_creation = &receipts[3];
1533        let sub4_creation = &receipts[4];
1534
1535        // ensure we are looking at the right receipts
1536        assert_eq!(sub1_creation.receiver_id, sub_account_1);
1537        assert_eq!(sub2_creation.receiver_id, sub_account_2);
1538        assert_eq!(sub3_creation.receiver_id, sub_account_3);
1539        assert_eq!(sub4_creation.receiver_id, sub_account_4);
1540
1541        // Find receipt indices to depend on
1542        let sub1_creation_index = sub1_creation.actions[0].receipt_index().unwrap();
1543        let sub2_creation_index = sub2_creation.actions[0].receipt_index().unwrap();
1544
1545        // Check dependencies were created
1546        assert!(
1547            main_account_creation.receipt_indices.is_empty(),
1548            "first receipt must not have dependencies"
1549        );
1550        assert_eq!(
1551            &sub1_creation.receipt_indices,
1552            &[0],
1553            "then_concurrent() must create dependency on receipt 0"
1554        );
1555        assert_eq!(
1556            &sub2_creation.receipt_indices,
1557            &[0],
1558            "then_concurrent() must create dependency on receipt 0"
1559        );
1560        assert_eq!(
1561            &sub3_creation.receipt_indices,
1562            &[sub1_creation_index, sub2_creation_index],
1563            "then_concurrent() must create dependency on sub1_creation_index + sub2_creation"
1564        );
1565        assert_eq!(
1566            &sub4_creation.receipt_indices,
1567            &[sub1_creation_index, sub2_creation_index],
1568            "then_concurrent() must create dependency on sub1_creation_index + sub2_creation"
1569        );
1570    }
1571
1572    #[test]
1573    #[should_panic(expected = "Cannot callback yielded promise.")]
1574    fn test_yielded_promise_cannot_be_continuation() {
1575        use crate::{Gas, GasWeight};
1576
1577        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1578
1579        let (yielded, _yield_id) =
1580            Promise::new_yield("callback", vec![], Gas::from_tgas(5), GasWeight(1));
1581
1582        let regular = Promise::new(alice()).create_account();
1583
1584        // This should panic - yielded promises cannot be used as continuations
1585        // i.e., they cannot appear on the right side of .then()
1586        regular.then(yielded).detach();
1587    }
1588
1589    #[test]
1590    fn test_new_yield_creates_promise_and_yield_id() {
1591        use crate::{Gas, GasWeight};
1592
1593        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1594
1595        // Create a yielded promise
1596        let (_promise, yield_id) =
1597            Promise::new_yield("on_callback", b"test_args", Gas::from_tgas(10), GasWeight(1));
1598
1599        // Verify yield_id is a valid 32-byte CryptoHash
1600        let yield_id_bytes: [u8; 32] = yield_id.0;
1601        // The mock generates a random yield ID, so just check it's not all zeros
1602        assert!(yield_id_bytes.iter().any(|&b| b != 0), "YieldId should not be all zeros");
1603    }
1604
1605    #[test]
1606    fn test_yield_id_is_unique() {
1607        use crate::{Gas, GasWeight};
1608
1609        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1610
1611        // Create multiple yielded promises and verify they get unique YieldIds
1612        let (_promise1, yield_id1) =
1613            Promise::new_yield("on_callback1", vec![], Gas::from_tgas(5), GasWeight(1));
1614        let (_promise2, yield_id2) =
1615            Promise::new_yield("on_callback2", vec![], Gas::from_tgas(5), GasWeight(1));
1616
1617        // The two yield IDs should be different
1618        assert_ne!(yield_id1, yield_id2, "Two yielded promises should have different YieldIds");
1619    }
1620
1621    #[test]
1622    #[ignore]
1623    //TODO: currently mock does not support yielded promises
1624    // uncomment after https://github.com/near/nearcore/pull/14792 is released
1625    fn test_yielded_promise_can_chain_then() {
1626        use crate::{Gas, GasWeight};
1627
1628        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1629
1630        // Create a yielded promise and chain another promise after it
1631        {
1632            let (yielded, _yield_id) =
1633                Promise::new_yield("on_resume", vec![], Gas::from_tgas(5), GasWeight(1));
1634
1635            // This should not panic - yielded promises CAN be first in a chain
1636            yielded.then(Promise::new(bob()).transfer(NearToken::from_yoctonear(1000))).detach();
1637        }
1638
1639        // Verify the chained promise was created (receipts are created on drop)
1640        let receipts = get_created_receipts();
1641        // The chained promise creates a receipt for bob()
1642        let bob_receipt = receipts
1643            .iter()
1644            .find(|r| r.receiver_id == bob())
1645            .expect("Should have created a receipt for bob()");
1646
1647        // Verify the exact transfer action in the chained receipt
1648        assert_eq!(bob_receipt.actions.len(), 1, "bob() receipt should have exactly 1 action");
1649        let receipt_index = bob_receipt.actions[0].receipt_index().unwrap();
1650        assert_eq!(
1651            bob_receipt.actions[0],
1652            MockAction::Transfer { receipt_index, deposit: NearToken::from_yoctonear(1000) }
1653        );
1654    }
1655}