1#[cfg(feature = "abi")]
2use borsh::BorshSchema;
3use std::cell::RefCell;
4#[cfg(any(feature = "abi", feature = "deterministic-account-ids"))]
5use std::collections::BTreeMap;
6use std::collections::VecDeque;
7use std::io::{Error, Write};
8use std::num::NonZeroU128;
9use std::rc::Rc;
10
11use crate::env::migrate_to_allowance;
12#[cfg(any(feature = "deterministic-account-ids", feature = "global-contracts"))]
13use crate::CryptoHash;
14use crate::{AccountId, Gas, GasWeight, NearToken, PromiseIndex, PublicKey};
15
16#[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 pub fn limited(balance: NearToken) -> Option<Allowance> {
31 NonZeroU128::new(balance.as_yoctonear()).map(Allowance::Limited)
32 }
33}
34
35enum PromiseAction {
36 CreateAccount,
37 DeployContract {
38 code: Vec<u8>,
39 },
40 FunctionCall {
41 function_name: String,
42 arguments: Vec<u8>,
43 amount: NearToken,
44 gas: Gas,
45 },
46 FunctionCallWeight {
47 function_name: String,
48 arguments: Vec<u8>,
49 amount: NearToken,
50 gas: Gas,
51 weight: GasWeight,
52 },
53 Transfer {
54 amount: NearToken,
55 },
56 Stake {
57 amount: NearToken,
58 public_key: PublicKey,
59 },
60 AddFullAccessKey {
61 public_key: PublicKey,
62 nonce: u64,
63 },
64 AddAccessKey {
65 public_key: PublicKey,
66 allowance: Allowance,
67 receiver_id: AccountId,
68 function_names: String,
69 nonce: u64,
70 },
71 DeleteKey {
72 public_key: PublicKey,
73 },
74 DeleteAccount {
75 beneficiary_id: AccountId,
76 },
77 #[cfg(feature = "global-contracts")]
78 DeployGlobalContract {
79 code: Vec<u8>,
80 },
81 #[cfg(feature = "global-contracts")]
82 DeployGlobalContractByAccountId {
83 code: Vec<u8>,
84 },
85 #[cfg(feature = "global-contracts")]
86 UseGlobalContract {
87 code_hash: CryptoHash,
88 },
89 #[cfg(feature = "global-contracts")]
90 UseGlobalContractByAccountId {
91 account_id: AccountId,
92 },
93 #[cfg(feature = "deterministic-account-ids")]
94 DeterministicStateInit {
95 state_init: crate::state_init::StateInit,
96 deposit: NearToken,
97 },
98}
99
100impl PromiseAction {
101 pub fn add(&self, promise_index: PromiseIndex) {
102 use PromiseAction::*;
103 match self {
104 CreateAccount => crate::env::promise_batch_action_create_account(promise_index),
105 DeployContract { code } => {
106 crate::env::promise_batch_action_deploy_contract(promise_index, code)
107 }
108 FunctionCall { function_name, arguments, amount, gas } => {
109 crate::env::promise_batch_action_function_call(
110 promise_index,
111 function_name,
112 arguments,
113 *amount,
114 *gas,
115 )
116 }
117 FunctionCallWeight { function_name, arguments, amount, gas, weight } => {
118 crate::env::promise_batch_action_function_call_weight(
119 promise_index,
120 function_name,
121 arguments,
122 *amount,
123 *gas,
124 GasWeight(weight.0),
125 )
126 }
127 Transfer { amount } => {
128 crate::env::promise_batch_action_transfer(promise_index, *amount)
129 }
130 Stake { amount, public_key } => {
131 crate::env::promise_batch_action_stake(promise_index, *amount, public_key)
132 }
133 AddFullAccessKey { public_key, nonce } => {
134 crate::env::promise_batch_action_add_key_with_full_access(
135 promise_index,
136 public_key,
137 *nonce,
138 )
139 }
140 AddAccessKey { public_key, allowance, receiver_id, function_names, nonce } => {
141 crate::env::promise_batch_action_add_key_allowance_with_function_call(
142 promise_index,
143 public_key,
144 *nonce,
145 *allowance,
146 receiver_id,
147 function_names,
148 )
149 }
150 DeleteKey { public_key } => {
151 crate::env::promise_batch_action_delete_key(promise_index, public_key)
152 }
153 DeleteAccount { beneficiary_id } => {
154 crate::env::promise_batch_action_delete_account(promise_index, beneficiary_id)
155 }
156 #[cfg(feature = "global-contracts")]
157 DeployGlobalContract { code } => {
158 crate::env::promise_batch_action_deploy_global_contract(promise_index, code)
159 }
160 #[cfg(feature = "global-contracts")]
161 DeployGlobalContractByAccountId { code } => {
162 crate::env::promise_batch_action_deploy_global_contract_by_account_id(
163 promise_index,
164 code,
165 )
166 }
167 #[cfg(feature = "global-contracts")]
168 UseGlobalContract { code_hash } => {
169 crate::env::promise_batch_action_use_global_contract(promise_index, code_hash)
170 }
171 #[cfg(feature = "global-contracts")]
172 UseGlobalContractByAccountId { account_id } => {
173 crate::env::promise_batch_action_use_global_contract_by_account_id(
174 promise_index,
175 account_id,
176 )
177 }
178 #[cfg(feature = "deterministic-account-ids")]
179 DeterministicStateInit {
180 state_init: crate::state_init::StateInit::V1(state_init),
181 deposit,
182 } => {
183 use crate::GlobalContractId;
184
185 let action_index = match &state_init.code {
186 GlobalContractId::CodeHash(code_hash) => {
187 crate::env::promise_batch_action_state_init(
188 promise_index,
189 *code_hash,
190 *deposit,
191 )
192 }
193 GlobalContractId::AccountId(account_id) => {
194 crate::env::promise_batch_action_state_init_by_account_id(
195 promise_index,
196 account_id,
197 *deposit,
198 )
199 }
200 };
201 for (key, value) in &state_init.data {
202 crate::env::set_state_init_data_entry(promise_index, action_index, key, value);
203 }
204 }
205 }
206 }
207}
208
209struct PromiseSingle {
210 pub account_id: AccountId,
211 pub actions: RefCell<Vec<PromiseAction>>,
212 pub after: RefCell<Option<Rc<Promise>>>,
213 pub promise_index: RefCell<Option<PromiseIndex>>,
215}
216
217impl PromiseSingle {
218 pub fn construct_recursively(&self) -> PromiseIndex {
219 let mut promise_lock = self.promise_index.borrow_mut();
220 if let Some(res) = promise_lock.as_ref() {
221 return *res;
222 }
223 let promise_index = if let Some(after) =
224 self.after.borrow().as_deref().and_then(Promise::construct_recursively)
225 {
226 crate::env::promise_batch_then(after, &self.account_id)
227 } else {
228 crate::env::promise_batch_create(&self.account_id)
229 };
230 let actions_lock = self.actions.borrow();
231 for action in actions_lock.iter() {
232 action.add(promise_index);
233 }
234 *promise_lock = Some(promise_index);
235 promise_index
236 }
237}
238
239pub struct PromiseJoint {
240 pub promises: RefCell<VecDeque<Promise>>,
241 pub promise_index: RefCell<Option<PromiseIndex>>,
243}
244
245impl PromiseJoint {
246 pub fn construct_recursively(&self) -> Option<PromiseIndex> {
247 let mut promise_lock = self.promise_index.borrow_mut();
248 if let Some(res) = promise_lock.as_ref() {
249 return Some(*res);
250 }
251 let promises_lock = self.promises.borrow();
252 if promises_lock.is_empty() {
253 return None;
254 }
255 let res = crate::env::promise_and(
256 &promises_lock.iter().filter_map(Promise::construct_recursively).collect::<Vec<_>>(),
257 );
258 *promise_lock = Some(res);
259 Some(res)
260 }
261}
262
263#[must_use = "return or detach explicitly via `.detach()`"]
305pub struct Promise {
306 subtype: PromiseSubtype,
307 should_return: RefCell<bool>,
308}
309
310#[cfg(feature = "abi")]
312impl BorshSchema for Promise {
313 fn add_definitions_recursively(
314 definitions: &mut BTreeMap<borsh::schema::Declaration, borsh::schema::Definition>,
315 ) {
316 <()>::add_definitions_recursively(definitions);
317 }
318
319 fn declaration() -> borsh::schema::Declaration {
320 <()>::declaration()
321 }
322}
323
324#[derive(Clone)]
325enum PromiseSubtype {
326 Single(Rc<PromiseSingle>),
327 Joint(Rc<PromiseJoint>),
328}
329
330impl Promise {
331 pub fn new(account_id: AccountId) -> Self {
334 Self {
335 subtype: PromiseSubtype::Single(Rc::new(PromiseSingle {
336 account_id,
337 actions: RefCell::new(vec![]),
338 after: RefCell::new(None),
339 promise_index: RefCell::new(None),
340 })),
341 should_return: RefCell::new(false),
342 }
343 }
344
345 fn add_action(self, action: PromiseAction) -> Self {
346 match &self.subtype {
347 PromiseSubtype::Single(x) => x.actions.borrow_mut().push(action),
348 PromiseSubtype::Joint(_) => {
349 crate::env::panic_str("Cannot add action to a joint promise.")
350 }
351 }
352 self
353 }
354
355 pub fn create_account(self) -> Self {
358 self.add_action(PromiseAction::CreateAccount)
359 }
360
361 pub fn deploy_contract(self, code: impl Into<Vec<u8>>) -> Self {
364 self.add_action(PromiseAction::DeployContract { code: code.into() })
365 }
366
367 #[cfg(feature = "global-contracts")]
368 pub fn deploy_global_contract(self, code: impl Into<Vec<u8>>) -> Self {
382 self.add_action(PromiseAction::DeployGlobalContract { code: code.into() })
383 }
384
385 #[cfg(feature = "global-contracts")]
386 pub fn deploy_global_contract_by_account_id(self, code: impl Into<Vec<u8>>) -> Self {
400 self.add_action(PromiseAction::DeployGlobalContractByAccountId { code: code.into() })
401 }
402
403 #[cfg(feature = "global-contracts")]
404 pub fn use_global_contract(self, code_hash: impl Into<CryptoHash>) -> Self {
418 self.add_action(PromiseAction::UseGlobalContract { code_hash: code_hash.into() })
419 }
420
421 #[cfg(feature = "global-contracts")]
422 pub fn use_global_contract_by_account_id(self, account_id: AccountId) -> Self {
435 self.add_action(PromiseAction::UseGlobalContractByAccountId { account_id })
436 }
437
438 #[cfg(feature = "deterministic-account-ids")]
440 pub fn state_init(self, state_init: crate::state_init::StateInit, deposit: NearToken) -> Self {
441 self.add_action(PromiseAction::DeterministicStateInit { state_init, deposit })
442 }
443
444 pub fn function_call(
447 self,
448 function_name: impl Into<String>,
449 arguments: impl Into<Vec<u8>>,
450 amount: NearToken,
451 gas: Gas,
452 ) -> Self {
453 self.add_action(PromiseAction::FunctionCall {
454 function_name: function_name.into(),
455 arguments: arguments.into(),
456 amount,
457 gas,
458 })
459 }
460
461 pub fn function_call_weight(
466 self,
467 function_name: impl Into<String>,
468 arguments: impl Into<Vec<u8>>,
469 amount: NearToken,
470 gas: Gas,
471 weight: GasWeight,
472 ) -> Self {
473 self.add_action(PromiseAction::FunctionCallWeight {
474 function_name: function_name.into(),
475 arguments: arguments.into(),
476 amount,
477 gas,
478 weight,
479 })
480 }
481
482 pub fn transfer(self, amount: NearToken) -> Self {
485 self.add_action(PromiseAction::Transfer { amount })
486 }
487
488 pub fn stake(self, amount: NearToken, public_key: PublicKey) -> Self {
491 self.add_action(PromiseAction::Stake { amount, public_key })
492 }
493
494 pub fn add_full_access_key(self, public_key: PublicKey) -> Self {
497 self.add_full_access_key_with_nonce(public_key, 0)
498 }
499
500 pub fn add_full_access_key_with_nonce(self, public_key: PublicKey, nonce: u64) -> Self {
503 self.add_action(PromiseAction::AddFullAccessKey { public_key, nonce })
504 }
505
506 pub fn add_access_key_allowance(
511 self,
512 public_key: PublicKey,
513 allowance: Allowance,
514 receiver_id: AccountId,
515 function_names: impl Into<String>,
516 ) -> Self {
517 self.add_access_key_allowance_with_nonce(
518 public_key,
519 allowance,
520 receiver_id,
521 function_names,
522 0,
523 )
524 }
525
526 #[deprecated(since = "5.0.0", note = "Use add_access_key_allowance instead")]
527 pub fn add_access_key(
528 self,
529 public_key: PublicKey,
530 allowance: NearToken,
531 receiver_id: AccountId,
532 function_names: impl Into<String>,
533 ) -> Self {
534 let allowance = migrate_to_allowance(allowance);
535 self.add_access_key_allowance(public_key, allowance, receiver_id, function_names)
536 }
537
538 pub fn add_access_key_allowance_with_nonce(
541 self,
542 public_key: PublicKey,
543 allowance: Allowance,
544 receiver_id: AccountId,
545 function_names: impl Into<String>,
546 nonce: u64,
547 ) -> Self {
548 self.add_action(PromiseAction::AddAccessKey {
549 public_key,
550 allowance,
551 receiver_id,
552 function_names: function_names.into(),
553 nonce,
554 })
555 }
556
557 #[deprecated(since = "5.0.0", note = "Use add_access_key_allowance_with_nonce instead")]
558 pub fn add_access_key_with_nonce(
559 self,
560 public_key: PublicKey,
561 allowance: NearToken,
562 receiver_id: AccountId,
563 function_names: impl Into<String>,
564 nonce: u64,
565 ) -> Self {
566 let allowance = migrate_to_allowance(allowance);
567 self.add_access_key_allowance_with_nonce(
568 public_key,
569 allowance,
570 receiver_id,
571 function_names,
572 nonce,
573 )
574 }
575
576 pub fn delete_key(self, public_key: PublicKey) -> Self {
579 self.add_action(PromiseAction::DeleteKey { public_key })
580 }
581
582 pub fn delete_account(self, beneficiary_id: AccountId) -> Self {
585 self.add_action(PromiseAction::DeleteAccount { beneficiary_id })
586 }
587
588 pub fn and(self, other: Promise) -> Promise {
603 match (&self.subtype, &other.subtype) {
604 (PromiseSubtype::Joint(x), PromiseSubtype::Joint(o)) => {
605 x.promises.borrow_mut().append(&mut o.promises.borrow_mut());
606 self
607 }
608 (PromiseSubtype::Joint(x), _) => {
609 x.promises.borrow_mut().push_back(other);
610 self
611 }
612 (_, PromiseSubtype::Joint(o)) => {
613 o.promises.borrow_mut().push_front(self);
614 other
615 }
616 _ => Promise {
617 subtype: PromiseSubtype::Joint(Rc::new(PromiseJoint {
618 promises: RefCell::new([self, other].into()),
619 promise_index: RefCell::new(None),
620 })),
621 should_return: RefCell::new(false),
622 },
623 }
624 }
625
626 pub fn then(self, other: Promise) -> Promise {
641 Rc::new(self).then_impl(other)
642 }
643
644 fn then_impl(self: Rc<Self>, other: Promise) -> Promise {
650 match &other.subtype {
651 PromiseSubtype::Single(x) => {
652 let mut after = x.after.borrow_mut();
653 if after.is_some() {
654 crate::env::panic_str(
655 "Cannot callback promise which is already scheduled after another",
656 );
657 }
658 *after = Some(self)
659 }
660 PromiseSubtype::Joint(_) => crate::env::panic_str("Cannot callback joint promise."),
661 }
662 other
663 }
664
665 pub fn then_concurrent(
698 self,
699 promises: impl IntoIterator<Item = Promise>,
700 ) -> ConcurrentPromises {
701 let this = Rc::new(self);
702 let mapped_promises =
703 promises.into_iter().map(|other| Rc::clone(&this).then_impl(other)).collect();
704 ConcurrentPromises { promises: mapped_promises }
705 }
706
707 #[allow(clippy::wrong_self_convention)]
735 pub fn as_return(self) -> Self {
736 *self.should_return.borrow_mut() = true;
737 self
738 }
739
740 fn construct_recursively(&self) -> Option<PromiseIndex> {
741 let res = match &self.subtype {
742 PromiseSubtype::Single(x) => x.construct_recursively(),
743 PromiseSubtype::Joint(x) => x.construct_recursively()?,
744 };
745 if *self.should_return.borrow() {
746 crate::env::promise_return(res);
747 }
748 Some(res)
749 }
750
751 #[inline]
753 pub fn detach(self) {}
754}
755
756impl Drop for Promise {
757 fn drop(&mut self) {
758 self.construct_recursively();
759 }
760}
761
762impl serde::Serialize for Promise {
763 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
764 where
765 S: serde::Serializer,
766 {
767 *self.should_return.borrow_mut() = true;
768 serializer.serialize_unit()
769 }
770}
771
772impl borsh::BorshSerialize for Promise {
773 fn serialize<W: Write>(&self, _writer: &mut W) -> Result<(), Error> {
774 *self.should_return.borrow_mut() = true;
775
776 Ok(())
779 }
780}
781
782#[cfg(feature = "abi")]
783impl schemars::JsonSchema for Promise {
784 fn schema_name() -> String {
785 "Promise".to_string()
786 }
787
788 fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
789 schemars::schema::Schema::Bool(true)
792 }
793}
794
795#[must_use = "return or detach explicitly via `.detach()`"]
813#[derive(serde::Serialize)]
814#[serde(untagged)]
815pub enum PromiseOrValue<T> {
816 Promise(Promise),
817 Value(T),
818}
819
820impl<T> PromiseOrValue<T> {
821 #[inline]
823 pub fn detach(self) {}
824}
825
826#[cfg(feature = "abi")]
827impl<T> BorshSchema for PromiseOrValue<T>
828where
829 T: BorshSchema,
830{
831 fn add_definitions_recursively(
832 definitions: &mut BTreeMap<borsh::schema::Declaration, borsh::schema::Definition>,
833 ) {
834 T::add_definitions_recursively(definitions);
835 }
836
837 fn declaration() -> borsh::schema::Declaration {
838 T::declaration()
839 }
840}
841
842impl<T> From<Promise> for PromiseOrValue<T> {
843 fn from(promise: Promise) -> Self {
844 PromiseOrValue::Promise(promise)
845 }
846}
847
848impl<T: borsh::BorshSerialize> borsh::BorshSerialize for PromiseOrValue<T> {
849 fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
850 match self {
851 PromiseOrValue::Value(x) => x.serialize(writer),
853 PromiseOrValue::Promise(p) => p.serialize(writer),
855 }
856 }
857}
858
859#[cfg(feature = "abi")]
860impl<T: schemars::JsonSchema> schemars::JsonSchema for PromiseOrValue<T> {
861 fn schema_name() -> String {
862 format!("PromiseOrValue{}", T::schema_name())
863 }
864
865 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
866 T::json_schema(gen)
867 }
868}
869
870#[must_use = "return or detach explicitly via `.detach()`"]
881pub struct ConcurrentPromises {
882 promises: Vec<Promise>,
883}
884
885impl ConcurrentPromises {
886 pub fn join(self) -> Promise {
892 self.promises
893 .into_iter()
894 .reduce(|left, right| left.and(right))
895 .expect("cannot join empty concurrent promises")
896 }
897
898 pub fn split_off(&mut self, at: usize) -> ConcurrentPromises {
905 let right_side = self.promises.split_off(at);
906 ConcurrentPromises { promises: right_side }
907 }
908
909 #[inline]
911 pub fn detach(self) {}
912}
913
914#[cfg(not(target_arch = "wasm32"))]
915#[cfg(test)]
916mod tests {
917 use crate::mock::MockAction;
918 use crate::test_utils::get_created_receipts;
919 use crate::test_utils::test_env::{alice, bob};
920 use crate::{
921 test_utils::VMContextBuilder, testing_env, AccountId, Allowance, NearToken, Promise,
922 PublicKey,
923 };
924
925 fn pk() -> PublicKey {
926 "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp".parse().unwrap()
927 }
928
929 fn get_actions() -> std::vec::IntoIter<MockAction> {
930 let receipts = get_created_receipts();
931 let first_receipt = receipts.into_iter().next().unwrap();
932 first_receipt.actions.into_iter()
933 }
934
935 fn has_add_key_with_full_access(public_key: PublicKey, nonce: Option<u64>) -> bool {
936 let public_key = near_crypto::PublicKey::try_from(public_key).unwrap();
937 get_actions().any(|el| {
938 matches!(
939 el,
940 MockAction::AddKeyWithFullAccess { public_key: p, nonce: n, receipt_index: _, }
941 if p == public_key
942 && (nonce.is_none() || Some(n) == nonce)
943 )
944 })
945 }
946
947 fn has_add_key_with_function_call(
948 public_key: PublicKey,
949 allowance: u128,
950 receiver_id: AccountId,
951 function_names: String,
952 nonce: Option<u64>,
953 ) -> bool {
954 let public_key = near_crypto::PublicKey::try_from(public_key).unwrap();
955 get_actions().any(|el| {
956 matches!(
957 el,
958 MockAction::AddKeyWithFunctionCall {
959 public_key: p,
960 allowance: a,
961 receiver_id: r,
962 method_names,
963 nonce: n,
964 receipt_index: _,
965 }
966 if p == public_key
967 && a.unwrap() == NearToken::from_yoctonear(allowance)
968 && r == receiver_id
969 && method_names.clone() == function_names.split(',').collect::<Vec<_>>()
970 && (nonce.is_none() || Some(n) == nonce)
971 )
972 })
973 }
974
975 #[test]
976 fn test_add_full_access_key() {
977 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
978
979 let public_key: PublicKey = pk();
980
981 {
984 Promise::new(alice()).create_account().add_full_access_key(public_key.clone()).detach();
985 }
986
987 assert!(has_add_key_with_full_access(public_key, None));
988 }
989
990 #[test]
991 fn test_add_full_access_key_with_nonce() {
992 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
993
994 let public_key: PublicKey = pk();
995 let nonce = 42;
996
997 {
998 Promise::new(alice())
999 .create_account()
1000 .add_full_access_key_with_nonce(public_key.clone(), nonce)
1001 .detach();
1002 }
1003
1004 assert!(has_add_key_with_full_access(public_key, Some(nonce)));
1005 }
1006
1007 #[test]
1008 fn test_add_access_key_allowance() {
1009 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1010
1011 let public_key: PublicKey = pk();
1012 let allowance = 100;
1013 let receiver_id = bob();
1014 let function_names = "method_a,method_b".to_string();
1015
1016 {
1017 Promise::new(alice())
1018 .create_account()
1019 .add_access_key_allowance(
1020 public_key.clone(),
1021 Allowance::Limited(allowance.try_into().unwrap()),
1022 receiver_id.clone(),
1023 function_names.clone(),
1024 )
1025 .detach();
1026 }
1027
1028 assert!(has_add_key_with_function_call(
1029 public_key,
1030 allowance,
1031 receiver_id,
1032 function_names,
1033 None
1034 ));
1035 }
1036
1037 #[test]
1038 fn test_add_access_key() {
1039 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1040
1041 let public_key: PublicKey = pk();
1042 let allowance = NearToken::from_yoctonear(100);
1043 let receiver_id = bob();
1044 let function_names = "method_a,method_b".to_string();
1045
1046 {
1047 #[allow(deprecated)]
1048 Promise::new(alice())
1049 .create_account()
1050 .add_access_key(
1051 public_key.clone(),
1052 allowance,
1053 receiver_id.clone(),
1054 function_names.clone(),
1055 )
1056 .detach();
1057 }
1058
1059 assert!(has_add_key_with_function_call(
1060 public_key,
1061 allowance.as_yoctonear(),
1062 receiver_id,
1063 function_names,
1064 None
1065 ));
1066 }
1067
1068 #[test]
1069 fn test_add_access_key_allowance_with_nonce() {
1070 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1071
1072 let public_key: PublicKey = pk();
1073 let allowance = 100;
1074 let receiver_id = bob();
1075 let function_names = "method_a,method_b".to_string();
1076 let nonce = 42;
1077
1078 {
1079 Promise::new(alice())
1080 .create_account()
1081 .add_access_key_allowance_with_nonce(
1082 public_key.clone(),
1083 Allowance::Limited(allowance.try_into().unwrap()),
1084 receiver_id.clone(),
1085 function_names.clone(),
1086 nonce,
1087 )
1088 .detach();
1089 }
1090
1091 assert!(has_add_key_with_function_call(
1092 public_key,
1093 allowance,
1094 receiver_id,
1095 function_names,
1096 Some(nonce)
1097 ));
1098 }
1099
1100 #[test]
1101 fn test_add_access_key_with_nonce() {
1102 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1103
1104 let public_key: PublicKey = pk();
1105 let allowance = NearToken::from_yoctonear(100);
1106 let receiver_id = bob();
1107 let function_names = "method_a,method_b".to_string();
1108 let nonce = 42;
1109
1110 {
1111 #[allow(deprecated)]
1112 Promise::new(alice())
1113 .create_account()
1114 .add_access_key_with_nonce(
1115 public_key.clone(),
1116 allowance,
1117 receiver_id.clone(),
1118 function_names.clone(),
1119 nonce,
1120 )
1121 .detach();
1122 }
1123
1124 assert!(has_add_key_with_function_call(
1125 public_key,
1126 allowance.as_yoctonear(),
1127 receiver_id,
1128 function_names,
1129 Some(nonce)
1130 ));
1131 }
1132
1133 #[test]
1134 fn test_delete_key() {
1135 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1136
1137 let public_key: PublicKey = pk();
1138
1139 {
1140 Promise::new(alice())
1141 .create_account()
1142 .add_full_access_key(public_key.clone())
1143 .delete_key(public_key.clone())
1144 .detach();
1145 }
1146 let public_key = near_crypto::PublicKey::try_from(public_key).unwrap();
1147
1148 let has_action = get_actions().any(|el| {
1149 matches!(
1150 el,
1151 MockAction::DeleteKey { public_key: p , receipt_index: _, } if p == public_key
1152 )
1153 });
1154 assert!(has_action);
1155 }
1156
1157 #[cfg(feature = "global-contracts")]
1158 #[test]
1159 fn test_deploy_global_contract() {
1160 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1161
1162 let code = vec![1, 2, 3, 4];
1163
1164 {
1165 Promise::new(alice()).create_account().deploy_global_contract(code.clone()).detach();
1166 }
1167
1168 let has_action = get_actions().any(|el| {
1169 matches!(
1170 el,
1171 MockAction::DeployGlobalContract { code: c, receipt_index: _, mode: _ } if c == code
1172 )
1173 });
1174 assert!(has_action);
1175 }
1176
1177 #[cfg(feature = "global-contracts")]
1178 #[test]
1179 fn test_deploy_global_contract_by_account_id() {
1180 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1181
1182 let code = vec![5, 6, 7, 8];
1183
1184 {
1185 Promise::new(alice())
1186 .create_account()
1187 .deploy_global_contract_by_account_id(code.clone())
1188 .detach();
1189 }
1190
1191 let has_action = get_actions().any(|el| {
1192 matches!(
1193 el,
1194 MockAction::DeployGlobalContract { code: c, receipt_index: _, mode: _ } if c == code
1195 )
1196 });
1197 assert!(has_action);
1198 }
1199
1200 #[cfg(feature = "global-contracts")]
1201 #[test]
1202 fn test_use_global_contract() {
1203 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1204
1205 let code_hash = [0u8; 32];
1206
1207 {
1208 Promise::new(alice()).create_account().use_global_contract(code_hash).detach();
1209 }
1210
1211 let has_action = get_actions().any(|el| matches!(el, MockAction::UseGlobalContract { .. }));
1213 assert!(has_action);
1214 }
1215
1216 #[cfg(feature = "global-contracts")]
1217 #[test]
1218 fn test_use_global_contract_by_account_id() {
1219 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1220
1221 let deployer = bob();
1222
1223 {
1224 Promise::new(alice())
1225 .create_account()
1226 .use_global_contract_by_account_id(deployer.clone())
1227 .detach();
1228 }
1229
1230 let has_action = get_actions().any(|el| {
1231 matches!(
1232 el,
1233 MockAction::UseGlobalContract {
1234 contract_id: near_primitives::action::GlobalContractIdentifier::AccountId(contract_id),
1235 receipt_index: _
1236 }
1237 if contract_id == deployer
1238 )
1239 });
1240 assert!(has_action);
1241 }
1242
1243 #[test]
1244 fn test_then() {
1245 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1246 let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1247
1248 {
1249 Promise::new(alice())
1250 .create_account()
1251 .then(Promise::new(sub_account_1).create_account())
1252 .detach();
1253 }
1254
1255 let receipts = get_created_receipts();
1256 let main_account_creation = &receipts[0];
1257 let sub_creation = &receipts[1];
1258
1259 assert!(
1260 main_account_creation.receipt_indices.is_empty(),
1261 "first receipt must not have dependencies"
1262 );
1263 assert_eq!(
1264 &sub_creation.receipt_indices,
1265 &[0],
1266 "then_concurrent() must create dependency on receipt 0"
1267 );
1268 }
1269
1270 #[test]
1271 fn test_then_concurrent() {
1272 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1273 let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1274 let sub_account_2: AccountId = "sub2.alice.near".parse().unwrap();
1275
1276 {
1277 let p1 = Promise::new(sub_account_1.clone()).create_account();
1278 let p2 = Promise::new(sub_account_2.clone()).create_account();
1279 Promise::new(alice()).create_account().then_concurrent(vec![p1, p2]).detach();
1280 }
1281
1282 let receipts = get_created_receipts();
1283 let main_account_creation = &receipts[0];
1284
1285 let sub1_creation = &receipts[1];
1286 let sub2_creation = &receipts[2];
1287
1288 assert_eq!(sub1_creation.receiver_id, sub_account_1);
1290 assert_eq!(sub2_creation.receiver_id, sub_account_2);
1291
1292 assert!(
1294 main_account_creation.receipt_indices.is_empty(),
1295 "first receipt must not have dependencies"
1296 );
1297 assert_eq!(
1298 &sub1_creation.receipt_indices,
1299 &[0],
1300 "then_concurrent() must create dependency on receipt 0"
1301 );
1302 assert_eq!(
1303 &sub2_creation.receipt_indices,
1304 &[0],
1305 "then_concurrent() must create dependency on receipt 0"
1306 );
1307 }
1308
1309 #[test]
1310 fn test_then_concurrent_split_off_then() {
1311 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1312 let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1313 let sub_account_2: AccountId = "sub2.alice.near".parse().unwrap();
1314 let sub_account_3: AccountId = "sub3.sub2.alice.near".parse().unwrap();
1315
1316 {
1317 let p1 = Promise::new(sub_account_1.clone()).create_account();
1318 let p2 = Promise::new(sub_account_2.clone()).create_account();
1319 let p3 = Promise::new(sub_account_3.clone()).create_account();
1320 Promise::new(alice())
1321 .create_account()
1322 .then_concurrent(vec![p1, p2])
1323 .split_off(1)
1324 .join()
1325 .then(p3)
1326 .detach();
1327 }
1328
1329 let receipts = get_created_receipts();
1330 let main_account_creation = &receipts[0];
1331
1332 let sub1_creation = &receipts[3];
1334 let sub2_creation = &receipts[1];
1335 let sub3_creation = &receipts[2];
1336
1337 assert_eq!(sub1_creation.receiver_id, sub_account_1);
1339 assert_eq!(sub2_creation.receiver_id, sub_account_2);
1340 assert_eq!(sub3_creation.receiver_id, sub_account_3);
1341
1342 let sub2_creation_index = sub2_creation.actions[0].receipt_index().unwrap();
1344
1345 assert!(
1347 main_account_creation.receipt_indices.is_empty(),
1348 "first receipt must not have dependencies"
1349 );
1350 assert_eq!(
1351 &sub1_creation.receipt_indices,
1352 &[0],
1353 "then_concurrent() must create dependency on receipt 0"
1354 );
1355 assert_eq!(
1356 &sub2_creation.receipt_indices,
1357 &[0],
1358 "then_concurrent() must create dependency on receipt 0"
1359 );
1360 assert_eq!(
1361 &sub3_creation.receipt_indices,
1362 &[sub2_creation_index],
1363 "then() must create dependency on sub2_creation"
1364 );
1365 }
1366
1367 #[test]
1368 fn test_then_concurrent_twice() {
1369 testing_env!(VMContextBuilder::new().signer_account_id(alice()).build());
1370 let sub_account_1: AccountId = "sub1.alice.near".parse().unwrap();
1371 let sub_account_2: AccountId = "sub2.alice.near".parse().unwrap();
1372 let sub_account_3: AccountId = "sub3.sub2.alice.near".parse().unwrap();
1373 let sub_account_4: AccountId = "sub4.sub2.alice.near".parse().unwrap();
1374
1375 {
1376 let p1 = Promise::new(sub_account_1.clone()).create_account();
1377 let p2 = Promise::new(sub_account_2.clone()).create_account();
1378 let p3 = Promise::new(sub_account_3.clone()).create_account();
1379 let p4 = Promise::new(sub_account_4.clone()).create_account();
1380 Promise::new(alice())
1381 .create_account()
1382 .then_concurrent(vec![p1, p2])
1383 .join()
1384 .then_concurrent(vec![p3, p4])
1385 .detach();
1386 }
1387
1388 let receipts = get_created_receipts();
1389 let main_account_creation = &receipts[0];
1390 let sub1_creation = &receipts[1];
1392 let sub2_creation = &receipts[2];
1393 let sub3_creation = &receipts[3];
1394 let sub4_creation = &receipts[4];
1395
1396 assert_eq!(sub1_creation.receiver_id, sub_account_1);
1398 assert_eq!(sub2_creation.receiver_id, sub_account_2);
1399 assert_eq!(sub3_creation.receiver_id, sub_account_3);
1400 assert_eq!(sub4_creation.receiver_id, sub_account_4);
1401
1402 let sub1_creation_index = sub1_creation.actions[0].receipt_index().unwrap();
1404 let sub2_creation_index = sub2_creation.actions[0].receipt_index().unwrap();
1405
1406 assert!(
1408 main_account_creation.receipt_indices.is_empty(),
1409 "first receipt must not have dependencies"
1410 );
1411 assert_eq!(
1412 &sub1_creation.receipt_indices,
1413 &[0],
1414 "then_concurrent() must create dependency on receipt 0"
1415 );
1416 assert_eq!(
1417 &sub2_creation.receipt_indices,
1418 &[0],
1419 "then_concurrent() must create dependency on receipt 0"
1420 );
1421 assert_eq!(
1422 &sub3_creation.receipt_indices,
1423 &[sub1_creation_index, sub2_creation_index],
1424 "then_concurrent() must create dependency on sub1_creation_index + sub2_creation"
1425 );
1426 assert_eq!(
1427 &sub4_creation.receipt_indices,
1428 &[sub1_creation_index, sub2_creation_index],
1429 "then_concurrent() must create dependency on sub1_creation_index + sub2_creation"
1430 );
1431 }
1432}