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#[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 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 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 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
186pub struct Promise {
226 subtype: PromiseSubtype,
227 should_return: RefCell<bool>,
228}
229
230#[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 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 pub fn create_account(self) -> Self {
276 self.add_action(PromiseAction::CreateAccount)
277 }
278
279 pub fn deploy_contract(self, code: Vec<u8>) -> Self {
281 self.add_action(PromiseAction::DeployContract { code })
282 }
283
284 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 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 pub fn transfer(self, amount: UncToken) -> Self {
317 self.add_action(PromiseAction::Transfer { amount })
318 }
319
320 pub fn stake(self, amount: UncToken, public_key: PublicKey) -> Self {
322 self.add_action(PromiseAction::Stake { amount, public_key })
323 }
324
325 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 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 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 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 pub fn delete_key(self, public_key: PublicKey) -> Self {
405 self.add_action(PromiseAction::DeleteKey { public_key })
406 }
407
408 pub fn delete_account(self, beneficiary_id: AccountId) -> Self {
410 self.add_action(PromiseAction::DeleteAccount { beneficiary_id })
411 }
412
413 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 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 #[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 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 schemars::schema::Schema::Bool(true)
546 }
547}
548
549#[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 PromiseOrValue::Value(x) => x.serialize(writer),
600 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 {
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}