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#[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
209enum PromiseSingleSubtype {
210 Regular {
211 account_id: AccountId,
212 after: RefCell<Option<Rc<Promise>>>,
213 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 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 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#[must_use = "return or detach explicitly via `.detach()`"]
325pub struct Promise {
326 subtype: PromiseSubtype,
327 should_return: RefCell<bool>,
328}
329
330#[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 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 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 pub fn create_account(self) -> Self {
432 self.add_action(PromiseAction::CreateAccount)
433 }
434
435 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 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 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 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 pub fn use_global_contract_by_account_id(self, account_id: AccountId) -> Self {
509 self.add_action(PromiseAction::UseGlobalContractByAccountId { account_id })
510 }
511
512 #[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 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 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 pub fn transfer(self, amount: NearToken) -> Self {
559 self.add_action(PromiseAction::Transfer { amount })
560 }
561
562 pub fn stake(self, amount: NearToken, public_key: PublicKey) -> Self {
565 self.add_action(PromiseAction::Stake { amount, public_key })
566 }
567
568 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 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 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 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 pub fn delete_key(self, public_key: PublicKey) -> Self {
653 self.add_action(PromiseAction::DeleteKey { public_key })
654 }
655
656 pub fn delete_account(self, beneficiary_id: AccountId) -> Self {
659 self.add_action(PromiseAction::DeleteAccount { beneficiary_id })
660 }
661
662 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 pub fn then(self, other: Promise) -> Promise {
715 Rc::new(self).then_impl(other)
716 }
717
718 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 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 #[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 #[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 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 schemars::schema::Schema::Bool(true)
871 }
872}
873
874#[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#[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 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#[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 #[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 PromiseOrValue::Value(x) => x.serialize(writer),
992 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#[must_use = "return or detach explicitly via `.detach()`"]
1020pub struct ConcurrentPromises {
1021 promises: Vec<Promise>,
1022}
1023
1024impl ConcurrentPromises {
1025 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 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 #[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 {
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 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 assert_eq!(sub1_creation.receiver_id, sub_account_1);
1429 assert_eq!(sub2_creation.receiver_id, sub_account_2);
1430
1431 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 let sub1_creation = &receipts[3];
1473 let sub2_creation = &receipts[1];
1474 let sub3_creation = &receipts[2];
1475
1476 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 let sub2_creation_index = sub2_creation.actions[0].receipt_index().unwrap();
1483
1484 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 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 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 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 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 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 let (_promise, yield_id) =
1597 Promise::new_yield("on_callback", b"test_args", Gas::from_tgas(10), GasWeight(1));
1598
1599 let yield_id_bytes: [u8; 32] = yield_id.0;
1601 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 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 assert_ne!(yield_id1, yield_id2, "Two yielded promises should have different YieldIds");
1619 }
1620
1621 #[test]
1622 #[ignore]
1623 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 {
1632 let (yielded, _yield_id) =
1633 Promise::new_yield("on_resume", vec![], Gas::from_tgas(5), GasWeight(1));
1634
1635 yielded.then(Promise::new(bob()).transfer(NearToken::from_yoctonear(1000))).detach();
1637 }
1638
1639 let receipts = get_created_receipts();
1641 let bob_receipt = receipts
1643 .iter()
1644 .find(|r| r.receiver_id == bob())
1645 .expect("Should have created a receipt for bob()");
1646
1647 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}