unc_sdk/
promise.rs

1#[cfg(feature = "abi")]
2use borsh::BorshSchema;
3use std::cell::RefCell;
4#[cfg(feature = "abi")]
5use std::collections::BTreeMap;
6use std::io::{Error, Write};
7use std::num::NonZeroU128;
8use std::rc::Rc;
9
10use crate::env::migrate_to_allowance;
11use crate::{AccountId, Gas, GasWeight, PromiseIndex, PublicKey, UncToken};
12
13/// Allow an access key to spend either an unlimited or limited amount of gas
14// This wrapper prevents incorrect construction
15#[derive(Clone, Copy)]
16pub enum Allowance {
17    Unlimited,
18    Limited(NonZeroU128),
19}
20
21impl Allowance {
22    pub fn unlimited() -> Allowance {
23        Allowance::Unlimited
24    }
25
26    /// This will return an None if you try to pass a zero value balance
27    pub fn limited(balance: UncToken) -> Option<Allowance> {
28        NonZeroU128::new(balance.as_attounc()).map(Allowance::Limited)
29    }
30}
31
32enum PromiseAction {
33    CreateAccount,
34    DeployContract {
35        code: Vec<u8>,
36    },
37    FunctionCall {
38        function_name: String,
39        arguments: Vec<u8>,
40        amount: UncToken,
41        gas: Gas,
42    },
43    FunctionCallWeight {
44        function_name: String,
45        arguments: Vec<u8>,
46        amount: UncToken,
47        gas: Gas,
48        weight: GasWeight,
49    },
50    Transfer {
51        amount: UncToken,
52    },
53    Stake {
54        amount: UncToken,
55        public_key: PublicKey,
56    },
57    AddFullAccessKey {
58        public_key: PublicKey,
59        nonce: u64,
60    },
61    AddAccessKey {
62        public_key: PublicKey,
63        allowance: Allowance,
64        receiver_id: AccountId,
65        function_names: String,
66        nonce: u64,
67    },
68    DeleteKey {
69        public_key: PublicKey,
70    },
71    DeleteAccount {
72        beneficiary_id: AccountId,
73    },
74}
75
76impl PromiseAction {
77    pub fn add(&self, promise_index: PromiseIndex) {
78        use PromiseAction::*;
79        match self {
80            CreateAccount => crate::env::promise_batch_action_create_account(promise_index),
81            DeployContract { code } => {
82                crate::env::promise_batch_action_deploy_contract(promise_index, code)
83            }
84            FunctionCall { function_name, arguments, amount, gas } => {
85                crate::env::promise_batch_action_function_call(
86                    promise_index,
87                    function_name,
88                    arguments,
89                    *amount,
90                    *gas,
91                )
92            }
93            FunctionCallWeight { function_name, arguments, amount, gas, weight } => {
94                crate::env::promise_batch_action_function_call_weight(
95                    promise_index,
96                    function_name,
97                    arguments,
98                    *amount,
99                    *gas,
100                    GasWeight(weight.0),
101                )
102            }
103            Transfer { amount } => {
104                crate::env::promise_batch_action_transfer(promise_index, *amount)
105            }
106            Stake { amount, public_key } => {
107                crate::env::promise_batch_action_stake(promise_index, *amount, public_key)
108            }
109            AddFullAccessKey { public_key, nonce } => {
110                crate::env::promise_batch_action_add_key_with_full_access(
111                    promise_index,
112                    public_key,
113                    *nonce,
114                )
115            }
116            AddAccessKey { public_key, allowance, receiver_id, function_names, nonce } => {
117                crate::env::promise_batch_action_add_key_allowance_with_function_call(
118                    promise_index,
119                    public_key,
120                    *nonce,
121                    *allowance,
122                    receiver_id,
123                    function_names,
124                )
125            }
126            DeleteKey { public_key } => {
127                crate::env::promise_batch_action_delete_key(promise_index, public_key)
128            }
129            DeleteAccount { beneficiary_id } => {
130                crate::env::promise_batch_action_delete_account(promise_index, beneficiary_id)
131            }
132        }
133    }
134}
135
136struct PromiseSingle {
137    pub account_id: AccountId,
138    pub actions: RefCell<Vec<PromiseAction>>,
139    pub after: RefCell<Option<Promise>>,
140    /// Promise index that is computed only once.
141    pub promise_index: RefCell<Option<PromiseIndex>>,
142}
143
144impl PromiseSingle {
145    pub fn construct_recursively(&self) -> PromiseIndex {
146        let mut promise_lock = self.promise_index.borrow_mut();
147        if let Some(res) = promise_lock.as_ref() {
148            return *res;
149        }
150        let promise_index = if let Some(after) = self.after.borrow().as_ref() {
151            crate::env::promise_batch_then(after.construct_recursively(), &self.account_id)
152        } else {
153            crate::env::promise_batch_create(&self.account_id)
154        };
155        let actions_lock = self.actions.borrow();
156        for action in actions_lock.iter() {
157            action.add(promise_index);
158        }
159        *promise_lock = Some(promise_index);
160        promise_index
161    }
162}
163
164pub struct PromiseJoint {
165    pub promise_a: Promise,
166    pub promise_b: Promise,
167    /// Promise index that is computed only once.
168    pub promise_index: RefCell<Option<PromiseIndex>>,
169}
170
171impl PromiseJoint {
172    pub fn construct_recursively(&self) -> PromiseIndex {
173        let mut promise_lock = self.promise_index.borrow_mut();
174        if let Some(res) = promise_lock.as_ref() {
175            return *res;
176        }
177        let res = crate::env::promise_and(&[
178            self.promise_a.construct_recursively(),
179            self.promise_b.construct_recursively(),
180        ]);
181        *promise_lock = Some(res);
182        res
183    }
184}
185
186/// A structure representing a result of the scheduled execution on another contract.
187///
188/// Smart contract developers will explicitly use `Promise` in two situations:
189/// * When they need to return `Promise`.
190///
191///   In the following code if someone calls method `ContractA::a` they will internally cause an
192///   execution of method `ContractB::b` of `bob` account, and the return value of `ContractA::a`
193///   will be what `ContractB::b` returned.
194/// ```no_run
195/// # use unc_sdk::{ext_contract, unc, Promise, Gas};
196/// #[ext_contract]
197/// pub trait ContractB {
198///     fn b(&mut self);
199/// }
200///
201/// #[unc(contract_state)]
202/// #[derive(Default)]
203/// struct ContractA {}
204///
205/// #[unc]
206/// impl ContractA {
207///     pub fn a(&self) -> Promise {
208///         contract_b::ext("bob".parse().unwrap()).b()
209///     }
210/// }
211/// ```
212///
213/// * When they need to create a transaction with one or many actions, e.g. the following code
214///   schedules a transaction that creates an account, transfers tokens, and assigns a public key:
215///
216/// ```no_run
217/// # use unc_sdk::{Promise, env, test_utils::VMContextBuilder, testing_env, Gas, UncToken};
218/// # testing_env!(VMContextBuilder::new().signer_account_id("bob".parse().unwrap())
219/// #               .account_balance(UncToken::from_attounc(1000)).prepaid_gas(Gas::from_gas(1_000_000)).build());
220/// Promise::new("bob".parse().unwrap())
221///   .create_account()
222///   .transfer(UncToken::from_attounc(1000))
223///   .add_full_access_key(env::signer_account_pk());
224/// ```
225pub struct Promise {
226    subtype: PromiseSubtype,
227    should_return: RefCell<bool>,
228}
229
230/// Until we implement strongly typed promises we serialize them as unit struct.
231#[cfg(feature = "abi")]
232impl BorshSchema for Promise {
233    fn add_definitions_recursively(
234        definitions: &mut BTreeMap<borsh::schema::Declaration, borsh::schema::Definition>,
235    ) {
236        <()>::add_definitions_recursively(definitions);
237    }
238
239    fn declaration() -> borsh::schema::Declaration {
240        <()>::declaration()
241    }
242}
243
244#[derive(Clone)]
245enum PromiseSubtype {
246    Single(Rc<PromiseSingle>),
247    Joint(Rc<PromiseJoint>),
248}
249
250impl Promise {
251    /// Create a promise that acts on the given account.
252    pub fn new(account_id: AccountId) -> Self {
253        Self {
254            subtype: PromiseSubtype::Single(Rc::new(PromiseSingle {
255                account_id,
256                actions: RefCell::new(vec![]),
257                after: RefCell::new(None),
258                promise_index: RefCell::new(None),
259            })),
260            should_return: RefCell::new(false),
261        }
262    }
263
264    fn add_action(self, action: PromiseAction) -> Self {
265        match &self.subtype {
266            PromiseSubtype::Single(x) => x.actions.borrow_mut().push(action),
267            PromiseSubtype::Joint(_) => {
268                crate::env::panic_str("Cannot add action to a joint promise.")
269            }
270        }
271        self
272    }
273
274    /// Create account on which this promise acts.
275    pub fn create_account(self) -> Self {
276        self.add_action(PromiseAction::CreateAccount)
277    }
278
279    /// Deploy a smart contract to the account on which this promise acts.
280    pub fn deploy_contract(self, code: Vec<u8>) -> Self {
281        self.add_action(PromiseAction::DeployContract { code })
282    }
283
284    /// A low-level interface for making a function call to the account that this promise acts on.
285    pub fn function_call(
286        self,
287        function_name: String,
288        arguments: Vec<u8>,
289        amount: UncToken,
290        gas: Gas,
291    ) -> Self {
292        self.add_action(PromiseAction::FunctionCall { function_name, arguments, amount, gas })
293    }
294
295    /// A low-level interface for making a function call to the account that this promise acts on.
296    /// unlike [`Promise::function_call`], this function accepts a weight to use relative unused gas
297    /// on this function call at the end of the scheduling method execution.
298    pub fn function_call_weight(
299        self,
300        function_name: String,
301        arguments: Vec<u8>,
302        amount: UncToken,
303        gas: Gas,
304        weight: GasWeight,
305    ) -> Self {
306        self.add_action(PromiseAction::FunctionCallWeight {
307            function_name,
308            arguments,
309            amount,
310            gas,
311            weight,
312        })
313    }
314
315    /// Transfer tokens to the account that this promise acts on.
316    pub fn transfer(self, amount: UncToken) -> Self {
317        self.add_action(PromiseAction::Transfer { amount })
318    }
319
320    /// Stake the account for the given amount of tokens using the given public key.
321    pub fn stake(self, amount: UncToken, public_key: PublicKey) -> Self {
322        self.add_action(PromiseAction::Stake { amount, public_key })
323    }
324
325    /// Add full access key to the given account.
326    pub fn add_full_access_key(self, public_key: PublicKey) -> Self {
327        self.add_full_access_key_with_nonce(public_key, 0)
328    }
329
330    /// Add full access key to the given account with a provided nonce.
331    pub fn add_full_access_key_with_nonce(self, public_key: PublicKey, nonce: u64) -> Self {
332        self.add_action(PromiseAction::AddFullAccessKey { public_key, nonce })
333    }
334
335    /// Add an access key that is restricted to only calling a smart contract on some account using
336    /// only a restricted set of methods. Here `function_names` is a comma separated list of methods,
337    /// e.g. `"method_a,method_b".to_string()`.
338    pub fn add_access_key_allowance(
339        self,
340        public_key: PublicKey,
341        allowance: Allowance,
342        receiver_id: AccountId,
343        function_names: String,
344    ) -> Self {
345        self.add_access_key_allowance_with_nonce(
346            public_key,
347            allowance,
348            receiver_id,
349            function_names,
350            0,
351        )
352    }
353
354    #[deprecated(since = "2.0.0", note = "Use add_access_key_allowance instead")]
355    pub fn add_access_key(
356        self,
357        public_key: PublicKey,
358        allowance: UncToken,
359        receiver_id: AccountId,
360        function_names: String,
361    ) -> Self {
362        let allowance = migrate_to_allowance(allowance);
363        self.add_access_key_allowance(public_key, allowance, receiver_id, function_names)
364    }
365
366    /// Add an access key with a provided nonce.
367    pub fn add_access_key_allowance_with_nonce(
368        self,
369        public_key: PublicKey,
370        allowance: Allowance,
371        receiver_id: AccountId,
372        function_names: String,
373        nonce: u64,
374    ) -> Self {
375        self.add_action(PromiseAction::AddAccessKey {
376            public_key,
377            allowance,
378            receiver_id,
379            function_names,
380            nonce,
381        })
382    }
383
384    #[deprecated(since = "2.0.0", note = "Use add_access_key_allowance_with_nonce instead")]
385    pub fn add_access_key_with_nonce(
386        self,
387        public_key: PublicKey,
388        allowance: UncToken,
389        receiver_id: AccountId,
390        function_names: String,
391        nonce: u64,
392    ) -> Self {
393        let allowance = migrate_to_allowance(allowance);
394        self.add_access_key_allowance_with_nonce(
395            public_key,
396            allowance,
397            receiver_id,
398            function_names,
399            nonce,
400        )
401    }
402
403    /// Delete access key from the given account.
404    pub fn delete_key(self, public_key: PublicKey) -> Self {
405        self.add_action(PromiseAction::DeleteKey { public_key })
406    }
407
408    /// Delete the given account.
409    pub fn delete_account(self, beneficiary_id: AccountId) -> Self {
410        self.add_action(PromiseAction::DeleteAccount { beneficiary_id })
411    }
412
413    /// Merge this promise with another promise, so that we can schedule execution of another
414    /// smart contract right after all merged promises finish.
415    ///
416    /// Note, once the promises are merged it is not possible to add actions to them, e.g. the
417    /// following code will panic during the execution of the smart contract:
418    ///
419    /// ```no_run
420    /// # use unc_sdk::{Promise, testing_env};
421    /// let p1 = Promise::new("bob".parse().unwrap()).create_account();
422    /// let p2 = Promise::new("carol".parse().unwrap()).create_account();
423    /// let p3 = p1.and(p2);
424    /// // p3.create_account();
425    /// ```
426    pub fn and(self, other: Promise) -> Promise {
427        Promise {
428            subtype: PromiseSubtype::Joint(Rc::new(PromiseJoint {
429                promise_a: self,
430                promise_b: other,
431                promise_index: RefCell::new(None),
432            })),
433            should_return: RefCell::new(false),
434        }
435    }
436
437    /// Schedules execution of another promise right after the current promise finish executing.
438    ///
439    /// In the following code `bob` and `dave` will be created concurrently. `carol`
440    /// creation will wait for `bob` to be created, and `eva` will wait for both `carol`
441    /// and `dave` to be created first.
442    /// ```no_run
443    /// # use unc_sdk::{Promise, VMContext, testing_env};
444    /// let p1 = Promise::new("bob".parse().unwrap()).create_account();
445    /// let p2 = Promise::new("carol".parse().unwrap()).create_account();
446    /// let p3 = Promise::new("dave".parse().unwrap()).create_account();
447    /// let p4 = Promise::new("eva".parse().unwrap()).create_account();
448    /// p1.then(p2).and(p3).then(p4);
449    /// ```
450    pub fn then(self, mut other: Promise) -> Promise {
451        match &mut other.subtype {
452            PromiseSubtype::Single(x) => {
453                let mut after = x.after.borrow_mut();
454                if after.is_some() {
455                    crate::env::panic_str(
456                        "Cannot callback promise which is already scheduled after another",
457                    );
458                }
459                *after = Some(self)
460            }
461            PromiseSubtype::Joint(_) => crate::env::panic_str("Cannot callback joint promise."),
462        }
463        other
464    }
465
466    /// A specialized, relatively low-level API method. Allows to mark the given promise as the one
467    /// that should be considered as a return value.
468    ///
469    /// In the below code `a1` and `a2` functions are equivalent.
470    /// ```
471    /// # use unc_sdk::{ext_contract, Gas, unc, Promise};
472    /// #[ext_contract]
473    /// pub trait ContractB {
474    ///     fn b(&mut self);
475    /// }
476    ///
477    /// #[unc(contract_state)]
478    /// #[derive(Default)]
479    /// struct ContractA {}
480    ///
481    /// #[unc]
482    /// impl ContractA {
483    ///     pub fn a1(&self) {
484    ///        contract_b::ext("bob".parse().unwrap()).b().as_return();
485    ///     }
486    ///
487    ///     pub fn a2(&self) -> Promise {
488    ///        contract_b::ext("bob".parse().unwrap()).b()
489    ///     }
490    /// }
491    /// ```
492    #[allow(clippy::wrong_self_convention)]
493    pub fn as_return(self) -> Self {
494        *self.should_return.borrow_mut() = true;
495        self
496    }
497
498    fn construct_recursively(&self) -> PromiseIndex {
499        let res = match &self.subtype {
500            PromiseSubtype::Single(x) => x.construct_recursively(),
501            PromiseSubtype::Joint(x) => x.construct_recursively(),
502        };
503        if *self.should_return.borrow() {
504            crate::env::promise_return(res);
505        }
506        res
507    }
508}
509
510impl Drop for Promise {
511    fn drop(&mut self) {
512        self.construct_recursively();
513    }
514}
515
516impl serde::Serialize for Promise {
517    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
518    where
519        S: serde::Serializer,
520    {
521        *self.should_return.borrow_mut() = true;
522        serializer.serialize_unit()
523    }
524}
525
526impl borsh::BorshSerialize for Promise {
527    fn serialize<W: Write>(&self, _writer: &mut W) -> Result<(), Error> {
528        *self.should_return.borrow_mut() = true;
529
530        // Intentionally no bytes written for the promise, the return value from the promise
531        // will be considered as the return value from the contract call.
532        Ok(())
533    }
534}
535
536#[cfg(feature = "abi")]
537impl schemars::JsonSchema for Promise {
538    fn schema_name() -> String {
539        "Promise".to_string()
540    }
541
542    fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
543        // Since promises are untyped, for now we represent Promise results with the schema
544        // `true` which matches everything (i.e. always passes validation)
545        schemars::schema::Schema::Bool(true)
546    }
547}
548
549/// When the method can return either a promise or a value, it can be called with `PromiseOrValue::Promise`
550/// or `PromiseOrValue::Value` to specify which one should be returned.
551/// # Example
552/// ```no_run
553/// # use unc_sdk::{ext_contract, unc, Gas, PromiseOrValue};
554/// #[ext_contract]
555/// pub trait ContractA {
556///     fn a(&mut self);
557/// }
558///
559/// let value = Some(true);
560/// let val: PromiseOrValue<bool> = if let Some(value) = value {
561///     PromiseOrValue::Value(value)
562/// } else {
563///     contract_a::ext("bob".parse().unwrap()).a().into()
564/// };
565/// ```
566#[derive(serde::Serialize)]
567#[serde(untagged)]
568pub enum PromiseOrValue<T> {
569    Promise(Promise),
570    Value(T),
571}
572
573#[cfg(feature = "abi")]
574impl<T> BorshSchema for PromiseOrValue<T>
575where
576    T: BorshSchema,
577{
578    fn add_definitions_recursively(
579        definitions: &mut BTreeMap<borsh::schema::Declaration, borsh::schema::Definition>,
580    ) {
581        T::add_definitions_recursively(definitions);
582    }
583
584    fn declaration() -> borsh::schema::Declaration {
585        T::declaration()
586    }
587}
588
589impl<T> From<Promise> for PromiseOrValue<T> {
590    fn from(promise: Promise) -> Self {
591        PromiseOrValue::Promise(promise)
592    }
593}
594
595impl<T: borsh::BorshSerialize> borsh::BorshSerialize for PromiseOrValue<T> {
596    fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
597        match self {
598            // Only actual value is serialized.
599            PromiseOrValue::Value(x) => x.serialize(writer),
600            // The promise is dropped to cause env::promise calls.
601            PromiseOrValue::Promise(p) => p.serialize(writer),
602        }
603    }
604}
605
606#[cfg(feature = "abi")]
607impl<T: schemars::JsonSchema> schemars::JsonSchema for PromiseOrValue<T> {
608    fn schema_name() -> String {
609        format!("PromiseOrValue{}", T::schema_name())
610    }
611
612    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
613        T::json_schema(gen)
614    }
615}
616
617#[cfg(not(target_arch = "wasm32"))]
618#[cfg(test)]
619mod tests {
620    use crate::mock::MockAction;
621    use crate::test_utils::get_created_receipts;
622    use crate::test_utils::test_env::{alice, bob};
623    use crate::{
624        test_utils::VMContextBuilder, testing_env, AccountId, Allowance, Promise, PublicKey,
625        UncToken,
626    };
627
628    fn pk() -> PublicKey {
629        "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp".parse().unwrap()
630    }
631
632    fn get_actions() -> std::vec::IntoIter<MockAction> {
633        let receipts = get_created_receipts();
634        let first_receipt = receipts.into_iter().next().unwrap();
635        first_receipt.actions.into_iter()
636    }
637
638    fn has_add_key_with_full_access(public_key: PublicKey, nonce: Option<u64>) -> bool {
639        let public_key = unc_crypto::PublicKey::try_from(public_key).unwrap();
640        get_actions().any(|el| {
641            matches!(
642                el,
643                MockAction::AddKeyWithFullAccess { public_key: p, nonce: n, receipt_index: _, }
644                if p == public_key
645                    && (nonce.is_none() || Some(n) == nonce)
646            )
647        })
648    }
649
650    fn has_add_key_with_function_call(
651        public_key: PublicKey,
652        allowance: u128,
653        receiver_id: AccountId,
654        function_names: String,
655        nonce: Option<u64>,
656    ) -> bool {
657        let public_key = unc_crypto::PublicKey::try_from(public_key).unwrap();
658        get_actions().any(|el| {
659            matches!(
660                el,
661                MockAction::AddKeyWithFunctionCall {
662                    public_key: p,
663                    allowance: a,
664                    receiver_id: r,
665                    method_names,
666                    nonce: n,
667                    receipt_index: _,
668                }
669                if p == public_key
670                    && a.unwrap() == UncToken::from_attounc(allowance)
671                    && r == receiver_id
672                    && method_names.clone() == function_names.split(',').collect::<Vec<_>>()
673                    && (nonce.is_none() || Some(n) == nonce)
674            )
675        })
676    }
677
678    #[test]
679    fn test_add_full_access_key() {
680        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
681
682        let public_key: PublicKey = pk();
683
684        // Promise is only executed when dropped so we put it in its own scope to make sure receipts
685        // are ready afterwards.
686        {
687            Promise::new(alice()).create_account().add_full_access_key(public_key.clone());
688        }
689
690        assert!(has_add_key_with_full_access(public_key, None));
691    }
692
693    #[test]
694    fn test_add_full_access_key_with_nonce() {
695        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
696
697        let public_key: PublicKey = pk();
698        let nonce = 42;
699
700        {
701            Promise::new(alice())
702                .create_account()
703                .add_full_access_key_with_nonce(public_key.clone(), nonce);
704        }
705
706        assert!(has_add_key_with_full_access(public_key, Some(nonce)));
707    }
708
709    #[test]
710    fn test_add_access_key_allowance() {
711        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
712
713        let public_key: PublicKey = pk();
714        let allowance = 100;
715        let receiver_id = bob();
716        let function_names = "method_a,method_b".to_string();
717
718        {
719            Promise::new(alice()).create_account().add_access_key_allowance(
720                public_key.clone(),
721                Allowance::Limited(allowance.try_into().unwrap()),
722                receiver_id.clone(),
723                function_names.clone(),
724            );
725        }
726
727        assert!(has_add_key_with_function_call(
728            public_key,
729            allowance,
730            receiver_id,
731            function_names,
732            None
733        ));
734    }
735
736    #[test]
737    fn test_add_access_key() {
738        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
739
740        let public_key: PublicKey = pk();
741        let allowance = UncToken::from_attounc(100);
742        let receiver_id = bob();
743        let function_names = "method_a,method_b".to_string();
744
745        {
746            #[allow(deprecated)]
747            Promise::new(alice()).create_account().add_access_key(
748                public_key.clone(),
749                allowance,
750                receiver_id.clone(),
751                function_names.clone(),
752            );
753        }
754
755        assert!(has_add_key_with_function_call(
756            public_key,
757            allowance.as_attounc(),
758            receiver_id,
759            function_names,
760            None
761        ));
762    }
763
764    #[test]
765    fn test_add_access_key_allowance_with_nonce() {
766        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
767
768        let public_key: PublicKey = pk();
769        let allowance = 100;
770        let receiver_id = bob();
771        let function_names = "method_a,method_b".to_string();
772        let nonce = 42;
773
774        {
775            Promise::new(alice()).create_account().add_access_key_allowance_with_nonce(
776                public_key.clone(),
777                Allowance::Limited(allowance.try_into().unwrap()),
778                receiver_id.clone(),
779                function_names.clone(),
780                nonce,
781            );
782        }
783
784        assert!(has_add_key_with_function_call(
785            public_key,
786            allowance,
787            receiver_id,
788            function_names,
789            Some(nonce)
790        ));
791    }
792
793    #[test]
794    fn test_add_access_key_with_nonce() {
795        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
796
797        let public_key: PublicKey = pk();
798        let allowance = UncToken::from_attounc(100);
799        let receiver_id = bob();
800        let function_names = "method_a,method_b".to_string();
801        let nonce = 42;
802
803        {
804            #[allow(deprecated)]
805            Promise::new(alice()).create_account().add_access_key_with_nonce(
806                public_key.clone(),
807                allowance,
808                receiver_id.clone(),
809                function_names.clone(),
810                nonce,
811            );
812        }
813
814        assert!(has_add_key_with_function_call(
815            public_key,
816            allowance.as_attounc(),
817            receiver_id,
818            function_names,
819            Some(nonce)
820        ));
821    }
822
823    #[test]
824    fn test_delete_key() {
825        testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
826
827        let public_key: PublicKey = pk();
828
829        {
830            Promise::new(alice())
831                .create_account()
832                .add_full_access_key(public_key.clone())
833                .delete_key(public_key.clone());
834        }
835        let public_key = unc_crypto::PublicKey::try_from(public_key).unwrap();
836
837        let has_action = get_actions().any(|el| {
838            matches!(
839                el,
840                MockAction::DeleteKey { public_key: p , receipt_index: _, } if p == public_key
841            )
842        });
843        assert!(has_action);
844    }
845}