radix_engine/blueprints/account/
blueprint.rs

1use super::*;
2use crate::blueprints::util::{PresecurifiedRoleAssignment, SecurifiedRoleAssignment};
3use crate::errors::ApplicationError;
4use crate::errors::RuntimeError;
5use crate::internal_prelude::*;
6use radix_engine_interface::api::field_api::LockFlags;
7use radix_engine_interface::api::FieldValue;
8use radix_engine_interface::api::{AttachedModuleId, GenericArgs, SystemApi, ACTOR_STATE_SELF};
9use radix_engine_interface::blueprints::account::*;
10use radix_engine_interface::blueprints::hooks::OnVirtualizeInput;
11use radix_engine_interface::blueprints::hooks::OnVirtualizeOutput;
12use radix_engine_interface::blueprints::resource::{Bucket, Proof};
13use radix_engine_interface::metadata_init;
14use radix_engine_interface::object_modules::metadata::*;
15use radix_native_sdk::modules::metadata::Metadata;
16use radix_native_sdk::modules::role_assignment::RoleAssignment;
17use radix_native_sdk::resource::NativeFungibleVault;
18use radix_native_sdk::resource::NativeNonFungibleVault;
19use radix_native_sdk::resource::NativeVault;
20use radix_native_sdk::resource::{NativeBucket, NativeNonFungibleBucket};
21use radix_native_sdk::runtime::Runtime;
22
23// =================================================================================================
24// Notes:
25// 1. All deposits should go through the `deposit` method since it emits the deposit events.
26// 2. The `try_deposit` methods are responsible for emitting the rejected deposit events.
27// =================================================================================================
28
29pub const ACCOUNT_CREATE_PREALLOCATED_SECP256K1_ID: u8 = 0u8;
30pub const ACCOUNT_CREATE_PREALLOCATED_ED25519_ID: u8 = 1u8;
31
32#[derive(Debug, PartialEq, Eq, ScryptoSbor, Clone)]
33pub struct AccountSubstate {
34    pub default_deposit_rule: DefaultDepositRule,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
38pub enum AccountError {
39    VaultDoesNotExist { resource_address: ResourceAddress },
40    DepositIsDisallowed { resource_address: ResourceAddress },
41    NotAllBucketsCouldBeDeposited,
42    NotAnAuthorizedDepositor { depositor: ResourceOrNonFungible },
43}
44
45impl From<AccountError> for RuntimeError {
46    fn from(value: AccountError) -> Self {
47        Self::ApplicationError(ApplicationError::AccountError(value))
48    }
49}
50
51pub const SECURIFY_ROLE: &'static str = "securify";
52
53struct SecurifiedAccount;
54
55impl SecurifiedRoleAssignment for SecurifiedAccount {
56    type OwnerBadgeNonFungibleData = AccountOwnerBadgeData;
57    const OWNER_BADGE: ResourceAddress = ACCOUNT_OWNER_BADGE;
58    const SECURIFY_ROLE: Option<&'static str> = Some(SECURIFY_ROLE);
59}
60
61impl PresecurifiedRoleAssignment for SecurifiedAccount {}
62
63declare_native_blueprint_state! {
64    blueprint_ident: Account,
65    blueprint_snake_case: account,
66    features: {
67    },
68    fields: {
69        deposit_rule:  {
70            ident: DepositRule,
71            field_type: {
72                kind: StaticSingleVersioned,
73            },
74            condition: Condition::Always,
75        }
76    },
77    collections: {
78        resource_vaults: KeyValue {
79            entry_ident: ResourceVault,
80            key_type: {
81                kind: Static,
82                content_type: ResourceAddress,
83            },
84            value_type: {
85                kind: StaticSingleVersioned,
86            },
87            allow_ownership: true,
88        },
89        resource_preferences: KeyValue {
90            entry_ident: ResourcePreference,
91            key_type: {
92                kind: Static,
93                content_type: ResourceAddress,
94            },
95            value_type: {
96                kind: StaticSingleVersioned,
97            },
98            allow_ownership: false,
99        },
100        authorized_depositors: KeyValue {
101            entry_ident: AuthorizedDepositor,
102            key_type: {
103                kind: Static,
104                content_type: ResourceOrNonFungible,
105            },
106            value_type: {
107                kind: StaticSingleVersioned,
108            },
109            allow_ownership: false,
110        },
111    }
112}
113
114pub type AccountDepositRuleV1 = AccountSubstate;
115pub type AccountResourceVaultV1 = Vault;
116pub type AccountResourcePreferenceV1 = ResourcePreference;
117pub type AccountAuthorizedDepositorV1 = ();
118
119pub struct AccountBlueprint;
120
121impl AccountBlueprint {
122    pub fn get_definition() -> BlueprintDefinitionInit {
123        let mut aggregator = TypeAggregator::<ScryptoCustomTypeKind>::new();
124
125        let feature_set = AccountFeatureSet::all_features();
126        let state = AccountStateSchemaInit::create_schema_init(&mut aggregator);
127
128        let mut functions = index_map_new();
129
130        functions.insert(
131            ACCOUNT_CREATE_ADVANCED_IDENT.to_string(),
132            FunctionSchemaInit {
133                receiver: None,
134                input: TypeRef::Static(
135                    aggregator.add_child_type_and_descendents::<AccountCreateAdvancedInput>(),
136                ),
137                output: TypeRef::Static(
138                    aggregator.add_child_type_and_descendents::<AccountCreateAdvancedOutput>(),
139                ),
140                export: ACCOUNT_CREATE_ADVANCED_IDENT.to_string(),
141            },
142        );
143
144        functions.insert(
145            ACCOUNT_CREATE_IDENT.to_string(),
146            FunctionSchemaInit {
147                receiver: None,
148                input: TypeRef::Static(
149                    aggregator.add_child_type_and_descendents::<AccountCreateInput>(),
150                ),
151                output: TypeRef::Static(
152                    aggregator.add_child_type_and_descendents::<AccountCreateOutput>(),
153                ),
154                export: ACCOUNT_CREATE_IDENT.to_string(),
155            },
156        );
157
158        functions.insert(
159            ACCOUNT_SECURIFY_IDENT.to_string(),
160            FunctionSchemaInit {
161                receiver: Some(ReceiverInfo::normal_ref_mut()),
162                input: TypeRef::Static(
163                    aggregator.add_child_type_and_descendents::<AccountSecurifyInput>(),
164                ),
165                output: TypeRef::Static(
166                    aggregator.add_child_type_and_descendents::<AccountSecurifyOutput>(),
167                ),
168                export: ACCOUNT_SECURIFY_IDENT.to_string(),
169            },
170        );
171
172        functions.insert(
173            ACCOUNT_LOCK_FEE_IDENT.to_string(),
174            FunctionSchemaInit {
175                receiver: Some(ReceiverInfo::normal_ref_mut()),
176                input: TypeRef::Static(
177                    aggregator.add_child_type_and_descendents::<AccountLockFeeInput>(),
178                ),
179                output: TypeRef::Static(
180                    aggregator.add_child_type_and_descendents::<AccountLockFeeOutput>(),
181                ),
182                export: ACCOUNT_LOCK_FEE_IDENT.to_string(),
183            },
184        );
185
186        functions.insert(
187            ACCOUNT_LOCK_CONTINGENT_FEE_IDENT.to_string(),
188            FunctionSchemaInit {
189                receiver: Some(ReceiverInfo::normal_ref_mut()),
190                input: TypeRef::Static(
191                    aggregator.add_child_type_and_descendents::<AccountLockContingentFeeInput>(),
192                ),
193                output: TypeRef::Static(
194                    aggregator.add_child_type_and_descendents::<AccountLockContingentFeeOutput>(),
195                ),
196                export: ACCOUNT_LOCK_CONTINGENT_FEE_IDENT.to_string(),
197            },
198        );
199
200        functions.insert(
201            ACCOUNT_DEPOSIT_IDENT.to_string(),
202            FunctionSchemaInit {
203                receiver: Some(ReceiverInfo::normal_ref_mut()),
204                input: TypeRef::Static(
205                    aggregator.add_child_type_and_descendents::<AccountDepositInput>(),
206                ),
207                output: TypeRef::Static(
208                    aggregator.add_child_type_and_descendents::<AccountDepositOutput>(),
209                ),
210                export: ACCOUNT_DEPOSIT_IDENT.to_string(),
211            },
212        );
213
214        functions.insert(
215            ACCOUNT_DEPOSIT_BATCH_IDENT.to_string(),
216            FunctionSchemaInit {
217                receiver: Some(ReceiverInfo::normal_ref_mut()),
218                input: TypeRef::Static(
219                    aggregator.add_child_type_and_descendents::<AccountDepositBatchInput>(),
220                ),
221                output: TypeRef::Static(
222                    aggregator.add_child_type_and_descendents::<AccountDepositBatchOutput>(),
223                ),
224                export: ACCOUNT_DEPOSIT_BATCH_IDENT.to_string(),
225            },
226        );
227
228        functions.insert(
229            ACCOUNT_WITHDRAW_IDENT.to_string(),
230            FunctionSchemaInit {
231                receiver: Some(ReceiverInfo::normal_ref_mut()),
232                input: TypeRef::Static(
233                    aggregator.add_child_type_and_descendents::<AccountWithdrawInput>(),
234                ),
235                output: TypeRef::Static(
236                    aggregator.add_child_type_and_descendents::<AccountWithdrawOutput>(),
237                ),
238                export: ACCOUNT_WITHDRAW_IDENT.to_string(),
239            },
240        );
241
242        functions.insert(
243            ACCOUNT_WITHDRAW_NON_FUNGIBLES_IDENT.to_string(),
244            FunctionSchemaInit {
245                receiver: Some(ReceiverInfo::normal_ref_mut()),
246                input: TypeRef::Static(
247                    aggregator.add_child_type_and_descendents::<AccountWithdrawNonFungiblesInput>(),
248                ),
249                output: TypeRef::Static(
250                    aggregator
251                        .add_child_type_and_descendents::<AccountWithdrawNonFungiblesOutput>(),
252                ),
253                export: ACCOUNT_WITHDRAW_NON_FUNGIBLES_IDENT.to_string(),
254            },
255        );
256
257        functions.insert(
258            ACCOUNT_BURN_IDENT.to_string(),
259            FunctionSchemaInit {
260                receiver: Some(ReceiverInfo::normal_ref_mut()),
261                input: TypeRef::Static(
262                    aggregator.add_child_type_and_descendents::<AccountBurnInput>(),
263                ),
264                output: TypeRef::Static(
265                    aggregator.add_child_type_and_descendents::<AccountBurnOutput>(),
266                ),
267                export: ACCOUNT_BURN_IDENT.to_string(),
268            },
269        );
270
271        functions.insert(
272            ACCOUNT_BURN_NON_FUNGIBLES_IDENT.to_string(),
273            FunctionSchemaInit {
274                receiver: Some(ReceiverInfo::normal_ref_mut()),
275                input: TypeRef::Static(
276                    aggregator.add_child_type_and_descendents::<AccountBurnNonFungiblesInput>(),
277                ),
278                output: TypeRef::Static(
279                    aggregator.add_child_type_and_descendents::<AccountBurnNonFungiblesOutput>(),
280                ),
281                export: ACCOUNT_BURN_NON_FUNGIBLES_IDENT.to_string(),
282            },
283        );
284
285        functions.insert(
286            ACCOUNT_LOCK_FEE_AND_WITHDRAW_IDENT.to_string(),
287            FunctionSchemaInit {
288                receiver: Some(ReceiverInfo::normal_ref_mut()),
289                input: TypeRef::Static(
290                    aggregator.add_child_type_and_descendents::<AccountLockFeeAndWithdrawInput>(),
291                ),
292                output: TypeRef::Static(
293                    aggregator.add_child_type_and_descendents::<AccountLockFeeAndWithdrawOutput>(),
294                ),
295                export: ACCOUNT_LOCK_FEE_AND_WITHDRAW_IDENT.to_string(),
296            },
297        );
298
299        functions.insert(
300            ACCOUNT_LOCK_FEE_AND_WITHDRAW_NON_FUNGIBLES_IDENT.to_string(),
301            FunctionSchemaInit {
302                receiver: Some(ReceiverInfo::normal_ref_mut()),
303                input: TypeRef::Static(aggregator
304                    .add_child_type_and_descendents::<AccountLockFeeAndWithdrawNonFungiblesInput>()),
305                output: TypeRef::Static(aggregator
306                    .add_child_type_and_descendents::<AccountLockFeeAndWithdrawNonFungiblesOutput>(
307                    )),
308                export: ACCOUNT_LOCK_FEE_AND_WITHDRAW_NON_FUNGIBLES_IDENT.to_string(),
309            },
310        );
311
312        functions.insert(
313            ACCOUNT_CREATE_PROOF_OF_AMOUNT_IDENT.to_string(),
314            FunctionSchemaInit {
315                receiver: Some(ReceiverInfo::normal_ref()),
316                input: TypeRef::Static(
317                    aggregator.add_child_type_and_descendents::<AccountCreateProofOfAmountInput>(),
318                ),
319                output: TypeRef::Static(
320                    aggregator.add_child_type_and_descendents::<AccountCreateProofOfAmountOutput>(),
321                ),
322                export: ACCOUNT_CREATE_PROOF_OF_AMOUNT_IDENT.to_string(),
323            },
324        );
325
326        functions.insert(
327            ACCOUNT_CREATE_PROOF_OF_NON_FUNGIBLES_IDENT.to_string(),
328            FunctionSchemaInit {
329                receiver: Some(ReceiverInfo::normal_ref()),
330                input: TypeRef::Static(
331                    aggregator
332                        .add_child_type_and_descendents::<AccountCreateProofOfNonFungiblesInput>(),
333                ),
334                output: TypeRef::Static(
335                    aggregator
336                        .add_child_type_and_descendents::<AccountCreateProofOfNonFungiblesOutput>(),
337                ),
338                export: ACCOUNT_CREATE_PROOF_OF_NON_FUNGIBLES_IDENT.to_string(),
339            },
340        );
341
342        functions.insert(
343            ACCOUNT_SET_DEFAULT_DEPOSIT_RULE_IDENT.to_string(),
344            FunctionSchemaInit {
345                receiver: Some(ReceiverInfo::normal_ref()),
346                input: TypeRef::Static(
347                    aggregator
348                        .add_child_type_and_descendents::<AccountSetDefaultDepositRuleInput>(),
349                ),
350                output: TypeRef::Static(
351                    aggregator
352                        .add_child_type_and_descendents::<AccountSetDefaultDepositRuleOutput>(),
353                ),
354                export: ACCOUNT_SET_DEFAULT_DEPOSIT_RULE_IDENT.to_string(),
355            },
356        );
357
358        functions.insert(
359            ACCOUNT_SET_RESOURCE_PREFERENCE_IDENT.to_string(),
360            FunctionSchemaInit {
361                receiver: Some(ReceiverInfo::normal_ref()),
362                input: TypeRef::Static(
363                    aggregator
364                        .add_child_type_and_descendents::<AccountSetResourcePreferenceInput>(),
365                ),
366                output: TypeRef::Static(
367                    aggregator
368                        .add_child_type_and_descendents::<AccountSetResourcePreferenceOutput>(),
369                ),
370                export: ACCOUNT_SET_RESOURCE_PREFERENCE_IDENT.to_string(),
371            },
372        );
373
374        functions.insert(
375            ACCOUNT_REMOVE_RESOURCE_PREFERENCE_IDENT.to_string(),
376            FunctionSchemaInit {
377                receiver: Some(ReceiverInfo::normal_ref()),
378                input: TypeRef::Static(
379                    aggregator
380                        .add_child_type_and_descendents::<AccountRemoveResourcePreferenceInput>(),
381                ),
382                output: TypeRef::Static(
383                    aggregator
384                        .add_child_type_and_descendents::<AccountRemoveResourcePreferenceOutput>(),
385                ),
386                export: ACCOUNT_REMOVE_RESOURCE_PREFERENCE_IDENT.to_string(),
387            },
388        );
389
390        functions.insert(
391            ACCOUNT_TRY_DEPOSIT_OR_REFUND_IDENT.to_string(),
392            FunctionSchemaInit {
393                receiver: Some(ReceiverInfo::normal_ref_mut()),
394                input: TypeRef::Static(
395                    aggregator.add_child_type_and_descendents::<AccountTryDepositOrRefundInput>(),
396                ),
397                output: TypeRef::Static(
398                    aggregator.add_child_type_and_descendents::<AccountTryDepositOrRefundOutput>(),
399                ),
400                export: ACCOUNT_TRY_DEPOSIT_OR_REFUND_IDENT.to_string(),
401            },
402        );
403
404        functions.insert(
405            ACCOUNT_TRY_DEPOSIT_BATCH_OR_REFUND_IDENT.to_string(),
406            FunctionSchemaInit {
407                receiver: Some(ReceiverInfo::normal_ref_mut()),
408                input: TypeRef::Static(
409                    aggregator
410                        .add_child_type_and_descendents::<AccountTryDepositBatchOrRefundInput>(),
411                ),
412                output: TypeRef::Static(
413                    aggregator
414                        .add_child_type_and_descendents::<AccountTryDepositBatchOrRefundOutput>(),
415                ),
416                export: ACCOUNT_TRY_DEPOSIT_BATCH_OR_REFUND_IDENT.to_string(),
417            },
418        );
419
420        functions.insert(
421            ACCOUNT_TRY_DEPOSIT_OR_ABORT_IDENT.to_string(),
422            FunctionSchemaInit {
423                receiver: Some(ReceiverInfo::normal_ref_mut()),
424                input: TypeRef::Static(
425                    aggregator.add_child_type_and_descendents::<AccountTryDepositOrAbortInput>(),
426                ),
427                output: TypeRef::Static(
428                    aggregator.add_child_type_and_descendents::<AccountTryDepositOrAbortOutput>(),
429                ),
430                export: ACCOUNT_TRY_DEPOSIT_OR_ABORT_IDENT.to_string(),
431            },
432        );
433
434        functions.insert(
435            ACCOUNT_TRY_DEPOSIT_BATCH_OR_ABORT_IDENT.to_string(),
436            FunctionSchemaInit {
437                receiver: Some(ReceiverInfo::normal_ref_mut()),
438                input: TypeRef::Static(
439                    aggregator
440                        .add_child_type_and_descendents::<AccountTryDepositBatchOrAbortInput>(),
441                ),
442                output: TypeRef::Static(
443                    aggregator
444                        .add_child_type_and_descendents::<AccountTryDepositBatchOrAbortOutput>(),
445                ),
446                export: ACCOUNT_TRY_DEPOSIT_BATCH_OR_ABORT_IDENT.to_string(),
447            },
448        );
449
450        functions.insert(
451            ACCOUNT_ADD_AUTHORIZED_DEPOSITOR_IDENT.to_string(),
452            FunctionSchemaInit {
453                receiver: Some(ReceiverInfo::normal_ref_mut()),
454                input: TypeRef::Static(
455                    aggregator
456                        .add_child_type_and_descendents::<AccountAddAuthorizedDepositorInput>(),
457                ),
458                output: TypeRef::Static(
459                    aggregator
460                        .add_child_type_and_descendents::<AccountAddAuthorizedDepositorOutput>(),
461                ),
462                export: ACCOUNT_ADD_AUTHORIZED_DEPOSITOR_IDENT.to_string(),
463            },
464        );
465
466        functions.insert(
467            ACCOUNT_REMOVE_AUTHORIZED_DEPOSITOR_IDENT.to_string(),
468            FunctionSchemaInit {
469                receiver: Some(ReceiverInfo::normal_ref_mut()),
470                input: TypeRef::Static(
471                    aggregator
472                        .add_child_type_and_descendents::<AccountRemoveAuthorizedDepositorInput>(),
473                ),
474                output: TypeRef::Static(
475                    aggregator
476                        .add_child_type_and_descendents::<AccountRemoveAuthorizedDepositorOutput>(),
477                ),
478                export: ACCOUNT_REMOVE_AUTHORIZED_DEPOSITOR_IDENT.to_string(),
479            },
480        );
481
482        let events = event_schema! {
483            aggregator,
484            [
485                WithdrawEvent,
486                DepositEvent,
487                RejectedDepositEvent,
488                SetResourcePreferenceEvent,
489                RemoveResourcePreferenceEvent,
490                SetDefaultDepositRuleEvent,
491                AddAuthorizedDepositorEvent,
492                RemoveAuthorizedDepositorEvent,
493            ]
494        };
495
496        let schema = generate_full_schema(aggregator);
497
498        BlueprintDefinitionInit {
499            blueprint_type: BlueprintType::default(),
500            is_transient: false,
501            feature_set,
502            dependencies: indexset!(
503                SECP256K1_SIGNATURE_RESOURCE.into(),
504                ED25519_SIGNATURE_RESOURCE.into(),
505                ACCOUNT_OWNER_BADGE.into(),
506                PACKAGE_OF_DIRECT_CALLER_RESOURCE.into(),
507            ),
508
509            schema: BlueprintSchemaInit {
510                generics: vec![],
511                schema,
512                state,
513                events,
514                types: BlueprintTypeSchemaInit::default(),
515                functions: BlueprintFunctionsSchemaInit { functions },
516                hooks: BlueprintHooksInit {
517                    hooks: indexmap!(BlueprintHook::OnVirtualize => ACCOUNT_ON_VIRTUALIZE_EXPORT_NAME.to_string()),
518                },
519            },
520
521            royalty_config: PackageRoyaltyConfig::default(),
522            auth_config: AuthConfig {
523                function_auth: FunctionAuth::AllowAll,
524                method_auth: MethodAuthTemplate::StaticRoleDefinition(roles_template!(
525                    roles {
526                        SECURIFY_ROLE => updaters: [SELF_ROLE];
527                    },
528                    methods {
529                        ACCOUNT_SECURIFY_IDENT => [SECURIFY_ROLE];
530
531                        ACCOUNT_SET_DEFAULT_DEPOSIT_RULE_IDENT => [OWNER_ROLE];
532                        ACCOUNT_SET_RESOURCE_PREFERENCE_IDENT => [OWNER_ROLE];
533                        ACCOUNT_REMOVE_RESOURCE_PREFERENCE_IDENT => [OWNER_ROLE];
534                        ACCOUNT_WITHDRAW_IDENT => [OWNER_ROLE];
535                        ACCOUNT_WITHDRAW_NON_FUNGIBLES_IDENT => [OWNER_ROLE];
536                        ACCOUNT_LOCK_FEE_IDENT => [OWNER_ROLE];
537                        ACCOUNT_LOCK_CONTINGENT_FEE_IDENT => [OWNER_ROLE];
538                        ACCOUNT_LOCK_FEE_AND_WITHDRAW_IDENT => [OWNER_ROLE];
539                        ACCOUNT_LOCK_FEE_AND_WITHDRAW_NON_FUNGIBLES_IDENT => [OWNER_ROLE];
540                        ACCOUNT_CREATE_PROOF_OF_AMOUNT_IDENT => [OWNER_ROLE];
541                        ACCOUNT_CREATE_PROOF_OF_NON_FUNGIBLES_IDENT => [OWNER_ROLE];
542                        ACCOUNT_DEPOSIT_IDENT => [OWNER_ROLE];
543                        ACCOUNT_DEPOSIT_BATCH_IDENT => [OWNER_ROLE];
544                        ACCOUNT_BURN_IDENT => [OWNER_ROLE];
545                        ACCOUNT_BURN_NON_FUNGIBLES_IDENT => [OWNER_ROLE];
546                        ACCOUNT_ADD_AUTHORIZED_DEPOSITOR_IDENT => [OWNER_ROLE];
547                        ACCOUNT_REMOVE_AUTHORIZED_DEPOSITOR_IDENT => [OWNER_ROLE];
548
549                        ACCOUNT_TRY_DEPOSIT_OR_REFUND_IDENT => MethodAccessibility::Public;
550                        ACCOUNT_TRY_DEPOSIT_BATCH_OR_REFUND_IDENT => MethodAccessibility::Public;
551                        ACCOUNT_TRY_DEPOSIT_OR_ABORT_IDENT => MethodAccessibility::Public;
552                        ACCOUNT_TRY_DEPOSIT_BATCH_OR_ABORT_IDENT => MethodAccessibility::Public;
553                    }
554                )),
555            },
556        }
557    }
558
559    fn create_modules<Y: SystemApi<RuntimeError>>(
560        role_assignment: RoleAssignment,
561        metadata_init: MetadataInit,
562        api: &mut Y,
563    ) -> Result<IndexMap<AttachedModuleId, Own>, RuntimeError> {
564        let metadata = Metadata::create_with_data(metadata_init, api)?;
565
566        // No component royalties
567        let modules = indexmap!(
568            AttachedModuleId::RoleAssignment => role_assignment.0,
569            AttachedModuleId::Metadata => metadata,
570        );
571
572        Ok(modules)
573    }
574
575    pub fn on_virtualize<Y: SystemApi<RuntimeError>>(
576        input: OnVirtualizeInput,
577        api: &mut Y,
578    ) -> Result<OnVirtualizeOutput, RuntimeError> {
579        match input.variant_id {
580            ACCOUNT_CREATE_PREALLOCATED_SECP256K1_ID => {
581                let public_key_hash = PublicKeyHash::Secp256k1(Secp256k1PublicKeyHash(input.rid));
582                Self::create_virtual(public_key_hash, input.address_reservation, api)
583            }
584            ACCOUNT_CREATE_PREALLOCATED_ED25519_ID => {
585                let public_key_hash = PublicKeyHash::Ed25519(Ed25519PublicKeyHash(input.rid));
586                Self::create_virtual(public_key_hash, input.address_reservation, api)
587            }
588            x => Err(RuntimeError::ApplicationError(
589                ApplicationError::PanicMessage(format!("Unexpected variant id: {:?}", x)),
590            )),
591        }
592    }
593
594    fn create_virtual<Y: SystemApi<RuntimeError>>(
595        public_key_hash: PublicKeyHash,
596        address_reservation: GlobalAddressReservation,
597        api: &mut Y,
598    ) -> Result<(), RuntimeError> {
599        let owner_badge = {
600            let bytes = public_key_hash.get_hash_bytes();
601            let entity_type = match public_key_hash {
602                PublicKeyHash::Ed25519(..) => EntityType::GlobalPreallocatedEd25519Account,
603                PublicKeyHash::Secp256k1(..) => EntityType::GlobalPreallocatedSecp256k1Account,
604            };
605
606            let mut id_bytes = vec![entity_type as u8];
607            id_bytes.extend(bytes);
608
609            NonFungibleLocalId::bytes(id_bytes).unwrap()
610        };
611
612        let account = Self::create_local(api)?;
613        let owner_id = NonFungibleGlobalId::from_public_key_hash(public_key_hash);
614        let role_assignment = SecurifiedAccount::create_presecurified(owner_id, api)?;
615        let modules = Self::create_modules(
616            role_assignment,
617            metadata_init!(
618                // NOTE:
619                // This is the owner key for ROLA. We choose to set this explicitly to simplify the
620                // security-critical logic off-ledger. In particular, we want an owner to be able to
621                // explicitly delete the owner keys. If we went with a "no metadata = assume default
622                // public key hash", then this could cause unexpected security-critical behavior if
623                // a user expected that deleting the metadata removed the owner keys.
624                "owner_keys" => vec![public_key_hash], updatable;
625                "owner_badge" => owner_badge, locked;
626            ),
627            api,
628        )?;
629
630        api.globalize(
631            account.0,
632            modules.into_iter().map(|(k, v)| (k, v.0)).collect(),
633            Some(address_reservation),
634        )?;
635        Ok(())
636    }
637
638    pub fn securify<Y: SystemApi<RuntimeError>>(api: &mut Y) -> Result<Bucket, RuntimeError> {
639        let receiver = Runtime::get_node_id(api)?;
640        let owner_badge_data = AccountOwnerBadgeData {
641            name: "Account Owner Badge".into(),
642            account: ComponentAddress::new_or_panic(receiver.0),
643        };
644        let bucket = SecurifiedAccount::securify(
645            &receiver,
646            owner_badge_data,
647            Some(NonFungibleLocalId::bytes(receiver.0).unwrap()),
648            api,
649        )?;
650        Ok(bucket.into())
651    }
652
653    pub fn create_advanced<Y: SystemApi<RuntimeError>>(
654        owner_role: OwnerRole,
655        address_reservation: Option<GlobalAddressReservation>,
656        api: &mut Y,
657    ) -> Result<GlobalAddress, RuntimeError> {
658        let account = Self::create_local(api)?;
659        let role_assignment = SecurifiedAccount::create_advanced(owner_role, api)?;
660        let modules = Self::create_modules(
661            role_assignment,
662            metadata_init!(
663                "owner_badge" => EMPTY, locked;
664            ),
665            api,
666        )?;
667        let modules = modules.into_iter().map(|(id, own)| (id, own.0)).collect();
668
669        let address = api.globalize(account.0, modules, address_reservation)?;
670
671        Ok(address)
672    }
673
674    pub fn create<Y: SystemApi<RuntimeError>>(
675        api: &mut Y,
676    ) -> Result<(GlobalAddress, Bucket), RuntimeError> {
677        let (address_reservation, address) = api.allocate_global_address(BlueprintId {
678            package_address: ACCOUNT_PACKAGE,
679            blueprint_name: ACCOUNT_BLUEPRINT.to_string(),
680        })?;
681
682        let account = Self::create_local(api)?;
683        let (role_assignment, bucket) = SecurifiedAccount::create_securified(
684            AccountOwnerBadgeData {
685                name: "Account Owner Badge".into(),
686                account: address.try_into().expect("Impossible Case"),
687            },
688            Some(NonFungibleLocalId::bytes(address.as_node_id().0).unwrap()),
689            api,
690        )?;
691        let modules = Self::create_modules(
692            role_assignment,
693            metadata_init! {
694                "owner_badge" => NonFungibleLocalId::bytes(address.as_node_id().0).unwrap(), locked;
695            },
696            api,
697        )?;
698        let modules = modules.into_iter().map(|(id, own)| (id, own.0)).collect();
699
700        let address = api.globalize(account.0, modules, Some(address_reservation))?;
701
702        Ok((address, bucket))
703    }
704
705    fn create_local<Y: SystemApi<RuntimeError>>(api: &mut Y) -> Result<Own, RuntimeError> {
706        let account_id = api.new_object(
707            ACCOUNT_BLUEPRINT,
708            vec![],
709            GenericArgs::default(),
710            indexmap! {
711                AccountField::DepositRule.field_index() => FieldValue::new(&AccountDepositRuleFieldPayload::from_content_source(AccountDepositRuleV1 {
712                    default_deposit_rule: DefaultDepositRule::Accept,
713                }))
714            },
715            indexmap!(),
716        )?;
717
718        Ok(Own(account_id))
719    }
720
721    fn lock_fee_internal<Y: SystemApi<RuntimeError>>(
722        amount: Decimal,
723        contingent: bool,
724        api: &mut Y,
725    ) -> Result<(), RuntimeError> {
726        let resource_address = XRD;
727
728        Self::get_vault(
729            resource_address,
730            |vault, api| {
731                if contingent {
732                    vault.lock_contingent_fee(api, amount)
733                } else {
734                    vault.lock_fee(api, amount)
735                }
736            },
737            false,
738            api,
739        )?;
740
741        Ok(())
742    }
743
744    pub fn lock_fee<Y: SystemApi<RuntimeError>>(
745        amount: Decimal,
746        api: &mut Y,
747    ) -> Result<(), RuntimeError> {
748        Self::lock_fee_internal(amount, false, api)?;
749        Ok(())
750    }
751
752    pub fn lock_contingent_fee<Y: SystemApi<RuntimeError>>(
753        amount: Decimal,
754        api: &mut Y,
755    ) -> Result<(), RuntimeError> {
756        Self::lock_fee_internal(amount, true, api)?;
757        Ok(())
758    }
759
760    /// Method requires auth - if call goes through it performs the deposit with no questions asked
761    pub fn deposit<Y: SystemApi<RuntimeError>>(
762        bucket: Bucket,
763        api: &mut Y,
764    ) -> Result<(), RuntimeError> {
765        let resource_address = bucket.resource_address(api)?;
766        let event = if resource_address.is_fungible() {
767            DepositEvent::Fungible(resource_address, bucket.amount(api)?)
768        } else {
769            DepositEvent::NonFungible(resource_address, bucket.non_fungible_local_ids(api)?)
770        };
771        Self::get_vault(
772            resource_address,
773            |vault, api| vault.put(bucket, api),
774            true,
775            api,
776        )?;
777        Runtime::emit_event(api, event)?;
778        Ok(())
779    }
780
781    /// Method requires auth - if call goes through it performs the deposit with no questions asked
782    pub fn deposit_batch<Y: SystemApi<RuntimeError>>(
783        buckets: Vec<Bucket>,
784        api: &mut Y,
785    ) -> Result<(), RuntimeError> {
786        for bucket in buckets {
787            Self::deposit(bucket, api)?;
788        }
789        Ok(())
790    }
791
792    pub fn try_deposit_or_refund<Y: SystemApi<RuntimeError>>(
793        bucket: Bucket,
794        authorized_depositor_badge: Option<ResourceOrNonFungible>,
795        api: &mut Y,
796    ) -> Result<Option<Bucket>, RuntimeError> {
797        let resource_address = bucket.resource_address(api)?;
798        let is_deposit_allowed = Self::is_deposit_allowed(&resource_address, api)?;
799        if is_deposit_allowed {
800            Self::deposit(bucket, api)?;
801            Ok(None)
802        } else if let Some(badge) = authorized_depositor_badge {
803            Self::validate_badge_is_authorized_depositor(&badge, api)??;
804            Self::validate_badge_is_present(badge, api)?;
805            Self::deposit(bucket, api)?;
806            Ok(None)
807        } else {
808            let event = if resource_address.is_fungible() {
809                RejectedDepositEvent::Fungible(resource_address, bucket.amount(api)?)
810            } else {
811                RejectedDepositEvent::NonFungible(
812                    resource_address,
813                    bucket.non_fungible_local_ids(api)?,
814                )
815            };
816            Runtime::emit_event(api, event)?;
817            Ok(Some(bucket))
818        }
819    }
820
821    pub fn try_deposit_batch_or_refund<Y: SystemApi<RuntimeError>>(
822        buckets: Vec<Bucket>,
823        authorized_depositor_badge: Option<ResourceOrNonFungible>,
824        api: &mut Y,
825    ) -> Result<Option<Vec<Bucket>>, RuntimeError> {
826        let offending_buckets = buckets
827            .iter()
828            .map(|bucket| {
829                bucket
830                    .resource_address(api)
831                    .and_then(|resource_address| Self::is_deposit_allowed(&resource_address, api))
832                    .map(|can_be_deposited| (bucket, can_be_deposited))
833            })
834            .collect::<Result<Vec<_>, _>>()?
835            .into_iter()
836            .filter_map(|(bucket, can_be_deposited)| {
837                if !can_be_deposited {
838                    Some(Bucket(bucket.0))
839                } else {
840                    None
841                }
842            })
843            .collect::<Vec<_>>();
844
845        if offending_buckets.is_empty() {
846            Self::deposit_batch(buckets, api)?;
847            Ok(None)
848        } else if let Some(badge) = authorized_depositor_badge {
849            Self::validate_badge_is_authorized_depositor(&badge, api)??;
850            Self::validate_badge_is_present(badge, api)?;
851            Self::deposit_batch(buckets, api)?;
852            Ok(None)
853        } else {
854            for bucket in offending_buckets {
855                let resource_address = bucket.resource_address(api)?;
856                let event = if resource_address.is_fungible() {
857                    RejectedDepositEvent::Fungible(resource_address, bucket.amount(api)?)
858                } else {
859                    RejectedDepositEvent::NonFungible(
860                        resource_address,
861                        bucket.non_fungible_local_ids(api)?,
862                    )
863                };
864                Runtime::emit_event(api, event)?;
865            }
866            Ok(Some(buckets))
867        }
868    }
869
870    pub fn try_deposit_or_abort<Y: SystemApi<RuntimeError>>(
871        bucket: Bucket,
872        authorized_depositor_badge: Option<ResourceOrNonFungible>,
873        api: &mut Y,
874    ) -> Result<(), RuntimeError> {
875        if let Some(bucket) = Self::try_deposit_or_refund(bucket, authorized_depositor_badge, api)?
876        {
877            let resource_address = bucket.resource_address(api)?;
878            Err(AccountError::DepositIsDisallowed { resource_address }.into())
879        } else {
880            Ok(())
881        }
882    }
883
884    /// Method is public to all - if ANY of the resources can't be deposited then the execution
885    /// panics.
886    pub fn try_deposit_batch_or_abort<Y: SystemApi<RuntimeError>>(
887        buckets: Vec<Bucket>,
888        authorized_depositor_badge: Option<ResourceOrNonFungible>,
889        api: &mut Y,
890    ) -> Result<(), RuntimeError> {
891        let buckets = Self::try_deposit_batch_or_refund(buckets, authorized_depositor_badge, api)?;
892        if let Some(_) = buckets {
893            Err(AccountError::NotAllBucketsCouldBeDeposited.into())
894        } else {
895            Ok(())
896        }
897    }
898
899    // Returns a result of a result. The outer result's error type is [`RuntimeError`] and it's for
900    // cases when something about the process fails, e.g., reading the KVStore fails for some reason
901    // or other cases. The inner result is for whether the validation succeeded or not.
902    fn validate_badge_is_authorized_depositor<Y: SystemApi<RuntimeError>>(
903        badge: &ResourceOrNonFungible,
904        api: &mut Y,
905    ) -> Result<Result<(), AccountError>, RuntimeError> {
906        // Read the account's authorized depositors to ensure that this badge is on the list of
907        // permitted depositors
908        let encoded_key =
909            scrypto_encode(badge).expect("Failed to SBOR encode a `ResourceOrNonFungible`.");
910        let kv_store_entry_lock_handle = api.actor_open_key_value_entry(
911            ACTOR_STATE_SELF,
912            AccountCollection::AuthorizedDepositorKeyValue.collection_index(),
913            &encoded_key,
914            LockFlags::read_only(),
915        )?;
916        let entry = api.key_value_entry_get_typed::<VersionedAccountAuthorizedDepositor>(
917            kv_store_entry_lock_handle,
918        )?;
919        api.key_value_entry_close(kv_store_entry_lock_handle)?;
920        if entry.is_none() {
921            Ok(Err(AccountError::NotAnAuthorizedDepositor {
922                depositor: badge.clone(),
923            }))
924        } else {
925            Ok(Ok(()))
926        }
927    }
928
929    fn validate_badge_is_present<Y: SystemApi<RuntimeError>>(
930        badge: ResourceOrNonFungible,
931        api: &mut Y,
932    ) -> Result<(), RuntimeError> {
933        // At this point we know that the badge is in the set of allowed depositors, so, we create
934        // an access rule and assert against it.
935        let access_rule = AccessRule::Protected(CompositeRequirement::BasicRequirement(
936            BasicRequirement::Require(badge),
937        ));
938
939        Runtime::assert_access_rule(access_rule, api)?;
940        Ok(())
941    }
942
943    pub fn withdraw<Y: SystemApi<RuntimeError>>(
944        resource_address: ResourceAddress,
945        amount: Decimal,
946        api: &mut Y,
947    ) -> Result<Bucket, RuntimeError> {
948        let bucket = Self::get_vault(
949            resource_address,
950            |vault, api| vault.take(amount, api),
951            false,
952            api,
953        )?;
954        let event = if resource_address.is_fungible() {
955            WithdrawEvent::Fungible(resource_address, bucket.amount(api)?)
956        } else {
957            WithdrawEvent::NonFungible(resource_address, bucket.non_fungible_local_ids(api)?)
958        };
959        Runtime::emit_event(api, event)?;
960
961        Ok(bucket)
962    }
963
964    pub fn withdraw_non_fungibles<Y: SystemApi<RuntimeError>>(
965        resource_address: ResourceAddress,
966        ids: IndexSet<NonFungibleLocalId>,
967        api: &mut Y,
968    ) -> Result<Bucket, RuntimeError> {
969        let bucket = Self::get_vault(
970            resource_address,
971            |vault, api| vault.take_non_fungibles(ids, api),
972            false,
973            api,
974        )?;
975        let event =
976            WithdrawEvent::NonFungible(resource_address, bucket.non_fungible_local_ids(api)?);
977        Runtime::emit_event(api, event)?;
978
979        Ok(bucket)
980    }
981
982    pub fn burn<Y: SystemApi<RuntimeError>>(
983        resource_address: ResourceAddress,
984        amount: Decimal,
985        api: &mut Y,
986    ) -> Result<(), RuntimeError> {
987        Self::get_vault(
988            resource_address,
989            |vault, api| vault.burn(amount, api),
990            false,
991            api,
992        )
993    }
994
995    pub fn burn_non_fungibles<Y: SystemApi<RuntimeError>>(
996        resource_address: ResourceAddress,
997        ids: IndexSet<NonFungibleLocalId>,
998        api: &mut Y,
999    ) -> Result<(), RuntimeError> {
1000        Self::get_vault(
1001            resource_address,
1002            |vault, api| vault.burn_non_fungibles(ids, api),
1003            false,
1004            api,
1005        )
1006    }
1007
1008    pub fn lock_fee_and_withdraw<Y: SystemApi<RuntimeError>>(
1009        amount_to_lock: Decimal,
1010        resource_address: ResourceAddress,
1011        amount: Decimal,
1012        api: &mut Y,
1013    ) -> Result<Bucket, RuntimeError> {
1014        Self::lock_fee_internal(amount_to_lock, false, api)?;
1015
1016        let bucket = Self::get_vault(
1017            resource_address,
1018            |vault, api| vault.take(amount, api),
1019            false,
1020            api,
1021        )?;
1022
1023        Ok(bucket)
1024    }
1025
1026    pub fn lock_fee_and_withdraw_non_fungibles<Y: SystemApi<RuntimeError>>(
1027        amount_to_lock: Decimal,
1028        resource_address: ResourceAddress,
1029        ids: IndexSet<NonFungibleLocalId>,
1030        api: &mut Y,
1031    ) -> Result<Bucket, RuntimeError> {
1032        Self::lock_fee_internal(amount_to_lock, false, api)?;
1033
1034        let bucket = Self::get_vault(
1035            resource_address,
1036            |vault, api| vault.take_non_fungibles(ids, api),
1037            false,
1038            api,
1039        )?;
1040
1041        Ok(bucket)
1042    }
1043
1044    pub fn create_proof_of_amount<Y: SystemApi<RuntimeError>>(
1045        resource_address: ResourceAddress,
1046        amount: Decimal,
1047        api: &mut Y,
1048    ) -> Result<Proof, RuntimeError> {
1049        let proof = Self::get_vault(
1050            resource_address,
1051            |vault, api| vault.create_proof_of_amount(amount, api),
1052            false,
1053            api,
1054        )?;
1055
1056        Ok(proof)
1057    }
1058
1059    pub fn create_proof_of_non_fungibles<Y: SystemApi<RuntimeError>>(
1060        resource_address: ResourceAddress,
1061        ids: IndexSet<NonFungibleLocalId>,
1062        api: &mut Y,
1063    ) -> Result<Proof, RuntimeError> {
1064        let proof = Self::get_vault(
1065            resource_address,
1066            |vault, api| vault.create_proof_of_non_fungibles(ids, api),
1067            false,
1068            api,
1069        )?;
1070
1071        Ok(proof)
1072    }
1073
1074    pub fn set_default_deposit_rule<Y: SystemApi<RuntimeError>>(
1075        default: DefaultDepositRule,
1076        api: &mut Y,
1077    ) -> Result<(), RuntimeError> {
1078        let handle = api.actor_open_field(
1079            ACTOR_STATE_SELF,
1080            AccountField::DepositRule.field_index(),
1081            LockFlags::MUTABLE,
1082        )?;
1083        api.field_write_typed(
1084            handle,
1085            &AccountDepositRuleFieldPayload::from_content_source(AccountDepositRuleV1 {
1086                default_deposit_rule: default,
1087            }),
1088        )?;
1089        api.field_close(handle)?;
1090
1091        Runtime::emit_event(
1092            api,
1093            SetDefaultDepositRuleEvent {
1094                default_deposit_rule: default,
1095            },
1096        )?;
1097
1098        Ok(())
1099    }
1100
1101    pub fn set_resource_preference<Y: SystemApi<RuntimeError>>(
1102        resource_address: ResourceAddress,
1103        resource_preference: ResourcePreference,
1104        api: &mut Y,
1105    ) -> Result<(), RuntimeError> {
1106        let encoded_key = scrypto_encode(&resource_address).expect("Impossible Case!");
1107        let kv_store_entry_lock_handle = api.actor_open_key_value_entry(
1108            ACTOR_STATE_SELF,
1109            AccountCollection::ResourcePreferenceKeyValue.collection_index(),
1110            &encoded_key,
1111            LockFlags::MUTABLE,
1112        )?;
1113        api.key_value_entry_set_typed(
1114            kv_store_entry_lock_handle,
1115            &AccountResourcePreferenceVersions::V1(resource_preference).into_versioned(),
1116        )?;
1117        api.key_value_entry_close(kv_store_entry_lock_handle)?;
1118
1119        Runtime::emit_event(
1120            api,
1121            SetResourcePreferenceEvent {
1122                resource_address,
1123                preference: resource_preference,
1124            },
1125        )?;
1126
1127        Ok(())
1128    }
1129
1130    pub fn remove_resource_preference<Y: SystemApi<RuntimeError>>(
1131        resource_address: ResourceAddress,
1132        api: &mut Y,
1133    ) -> Result<(), RuntimeError> {
1134        let encoded_key = scrypto_encode(&resource_address).expect("Impossible Case!");
1135        api.actor_remove_key_value_entry(
1136            ACTOR_STATE_SELF,
1137            AccountCollection::ResourcePreferenceKeyValue.collection_index(),
1138            &encoded_key,
1139        )?;
1140
1141        Runtime::emit_event(api, RemoveResourcePreferenceEvent { resource_address })?;
1142
1143        Ok(())
1144    }
1145
1146    pub fn add_authorized_depositor<Y: SystemApi<RuntimeError>>(
1147        badge: ResourceOrNonFungible,
1148        api: &mut Y,
1149    ) -> Result<(), RuntimeError> {
1150        let encoded_key =
1151            scrypto_encode(&badge).expect("Failed to SBOR encode a `ResourceOrNonFungible`.");
1152        let kv_store_entry_lock_handle = api.actor_open_key_value_entry(
1153            ACTOR_STATE_SELF,
1154            AccountCollection::AuthorizedDepositorKeyValue.collection_index(),
1155            &encoded_key,
1156            LockFlags::MUTABLE,
1157        )?;
1158        api.key_value_entry_set_typed(
1159            kv_store_entry_lock_handle,
1160            &AccountAuthorizedDepositorEntryPayload::from_content_source(()),
1161        )?;
1162        api.key_value_entry_close(kv_store_entry_lock_handle)?;
1163
1164        Runtime::emit_event(
1165            api,
1166            AddAuthorizedDepositorEvent {
1167                authorized_depositor_badge: badge,
1168            },
1169        )?;
1170
1171        Ok(())
1172    }
1173
1174    pub fn remove_authorized_depositor<Y: SystemApi<RuntimeError>>(
1175        badge: ResourceOrNonFungible,
1176        api: &mut Y,
1177    ) -> Result<(), RuntimeError> {
1178        let encoded_key =
1179            scrypto_encode(&badge).expect("Failed to SBOR encode a `ResourceOrNonFungible`.");
1180        api.actor_remove_key_value_entry(
1181            ACTOR_STATE_SELF,
1182            AccountCollection::AuthorizedDepositorKeyValue.collection_index(),
1183            &encoded_key,
1184        )?;
1185
1186        Runtime::emit_event(
1187            api,
1188            RemoveAuthorizedDepositorEvent {
1189                authorized_depositor_badge: badge,
1190            },
1191        )?;
1192
1193        Ok(())
1194    }
1195
1196    fn get_default_deposit_rule<Y: SystemApi<RuntimeError>>(
1197        api: &mut Y,
1198    ) -> Result<DefaultDepositRule, RuntimeError> {
1199        let handle = api.actor_open_field(
1200            ACTOR_STATE_SELF,
1201            AccountField::DepositRule.field_index(),
1202            LockFlags::read_only(),
1203        )?;
1204        let deposit_rule = api
1205            .field_read_typed::<AccountDepositRuleFieldPayload>(handle)?
1206            .fully_update_and_into_latest_version();
1207        let default = deposit_rule.default_deposit_rule;
1208        api.field_close(handle)?;
1209
1210        Ok(default)
1211    }
1212
1213    fn get_vault<Y: SystemApi<RuntimeError>, R>(
1214        resource_address: ResourceAddress,
1215        vault_fn: impl FnOnce(&mut Vault, &mut Y) -> Result<R, RuntimeError>,
1216        create: bool,
1217        api: &mut Y,
1218    ) -> Result<R, RuntimeError> {
1219        let encoded_key = scrypto_encode(&resource_address).expect("Impossible Case!");
1220
1221        let mut kv_store_entry_lock_handle = api.actor_open_key_value_entry(
1222            ACTOR_STATE_SELF,
1223            AccountCollection::ResourceVaultKeyValue.collection_index(),
1224            &encoded_key,
1225            LockFlags::read_only(),
1226        )?;
1227
1228        // Get the vault stored in the KeyValueStore entry - if it doesn't exist, then create it if
1229        // instructed to.
1230        let vault = {
1231            let entry = api
1232                .key_value_entry_get_typed::<AccountResourceVaultEntryPayload>(
1233                    kv_store_entry_lock_handle,
1234                )?
1235                .map(|v| v.fully_update_and_into_latest_version());
1236
1237            match entry {
1238                Some(vault) => Ok(vault),
1239                None => {
1240                    if create {
1241                        api.key_value_entry_close(kv_store_entry_lock_handle)?;
1242                        kv_store_entry_lock_handle = api.actor_open_key_value_entry(
1243                            ACTOR_STATE_SELF,
1244                            AccountCollection::ResourceVaultKeyValue.collection_index(),
1245                            &encoded_key,
1246                            LockFlags::MUTABLE,
1247                        )?;
1248                        let vault = Vault::create(resource_address, api)?;
1249                        let own = vault.0;
1250                        api.key_value_entry_set_typed(
1251                            kv_store_entry_lock_handle,
1252                            &AccountResourceVaultEntryPayload::from_content_source(vault),
1253                        )?;
1254                        Ok(Vault(own))
1255                    } else {
1256                        Err(AccountError::VaultDoesNotExist { resource_address })
1257                    }
1258                }
1259            }
1260        };
1261
1262        if let Ok(mut vault) = vault {
1263            match vault_fn(&mut vault, api) {
1264                Ok(rtn) => {
1265                    api.key_value_entry_close(kv_store_entry_lock_handle)?;
1266                    Ok(rtn)
1267                }
1268                Err(error) => Err(error),
1269            }
1270        } else {
1271            api.key_value_entry_close(kv_store_entry_lock_handle)?;
1272            Err(vault.unwrap_err().into())
1273        }
1274    }
1275
1276    fn is_deposit_allowed<Y: SystemApi<RuntimeError>>(
1277        resource_address: &ResourceAddress,
1278        api: &mut Y,
1279    ) -> Result<bool, RuntimeError> {
1280        match Self::get_resource_preference(resource_address, api)? {
1281            Some(ResourcePreference::Allowed) => Ok(true),
1282            Some(ResourcePreference::Disallowed) => Ok(false),
1283            None => {
1284                let default = Self::get_default_deposit_rule(api)?;
1285                match default {
1286                    DefaultDepositRule::Accept => Ok(true),
1287                    DefaultDepositRule::Reject => Ok(false),
1288                    DefaultDepositRule::AllowExisting => {
1289                        Ok(*resource_address == XRD
1290                            || Self::does_vault_exist(resource_address, api)?)
1291                    }
1292                }
1293            }
1294        }
1295    }
1296
1297    fn does_vault_exist<Y: SystemApi<RuntimeError>>(
1298        resource_address: &ResourceAddress,
1299        api: &mut Y,
1300    ) -> Result<bool, RuntimeError> {
1301        let encoded_key = scrypto_encode(resource_address).expect("Impossible Case!");
1302
1303        let kv_store_entry_lock_handle = api.actor_open_key_value_entry(
1304            ACTOR_STATE_SELF,
1305            AccountCollection::ResourceVaultKeyValue.collection_index(),
1306            &encoded_key,
1307            LockFlags::read_only(),
1308        )?;
1309
1310        let does_vault_exist = {
1311            let entry = api.key_value_entry_get_typed::<AccountResourceVaultEntryPayload>(
1312                kv_store_entry_lock_handle,
1313            )?;
1314            entry.is_some()
1315        };
1316
1317        api.key_value_entry_close(kv_store_entry_lock_handle)?;
1318
1319        Ok(does_vault_exist)
1320    }
1321
1322    fn get_resource_preference<Y: SystemApi<RuntimeError>>(
1323        resource_address: &ResourceAddress,
1324        api: &mut Y,
1325    ) -> Result<Option<ResourcePreference>, RuntimeError> {
1326        let encoded_key = scrypto_encode(&resource_address).expect("Impossible Case!");
1327
1328        let kv_store_entry_lock_handle = api.actor_open_key_value_entry(
1329            ACTOR_STATE_SELF,
1330            AccountCollection::ResourcePreferenceKeyValue.collection_index(),
1331            &encoded_key,
1332            LockFlags::read_only(),
1333        )?;
1334
1335        let entry = api
1336            .key_value_entry_get_typed::<AccountResourcePreferenceEntryPayload>(
1337                kv_store_entry_lock_handle,
1338            )?
1339            .map(|v| v.fully_update_and_into_latest_version());
1340        api.key_value_entry_close(kv_store_entry_lock_handle)?;
1341        Ok(entry)
1342    }
1343}
1344
1345#[derive(ScryptoSbor)]
1346pub struct AccountOwnerBadgeData {
1347    pub name: String,
1348    pub account: ComponentAddress,
1349}
1350
1351impl NonFungibleData for AccountOwnerBadgeData {
1352    const MUTABLE_FIELDS: &'static [&'static str] = &[];
1353}
1354
1355pub struct AccountBlueprintBottlenoseExtension;
1356
1357impl AccountBlueprintBottlenoseExtension {
1358    pub fn invoke_export<Y: SystemApi<RuntimeError>>(
1359        export_name: &str,
1360        input: &IndexedScryptoValue,
1361        api: &mut Y,
1362    ) -> Result<IndexedScryptoValue, RuntimeError> {
1363        match export_name {
1364            ACCOUNT_TRY_DEPOSIT_OR_REFUND_IDENT => {
1365                let AccountTryDepositOrRefundInput {
1366                    bucket,
1367                    authorized_depositor_badge,
1368                } = input.as_typed().map_err(|e| {
1369                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
1370                })?;
1371
1372                let rtn = Self::try_deposit_or_refund(bucket, authorized_depositor_badge, api)?;
1373                Ok(IndexedScryptoValue::from_typed(&rtn))
1374            }
1375            ACCOUNT_TRY_DEPOSIT_BATCH_OR_REFUND_IDENT => {
1376                let AccountTryDepositBatchOrRefundInput {
1377                    buckets,
1378                    authorized_depositor_badge,
1379                } = input.as_typed().map_err(|e| {
1380                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
1381                })?;
1382
1383                let rtn =
1384                    Self::try_deposit_batch_or_refund(buckets, authorized_depositor_badge, api)?;
1385                Ok(IndexedScryptoValue::from_typed(&rtn))
1386            }
1387            _ => Err(RuntimeError::ApplicationError(
1388                ApplicationError::ExportDoesNotExist(export_name.to_string()),
1389            )),
1390        }
1391    }
1392
1393    pub fn try_deposit_or_refund<Y: SystemApi<RuntimeError>>(
1394        bucket: Bucket,
1395        authorized_depositor_badge: Option<ResourceOrNonFungible>,
1396        api: &mut Y,
1397    ) -> Result<Option<Bucket>, RuntimeError> {
1398        let resource_address = bucket.resource_address(api)?;
1399        let is_deposit_allowed = AccountBlueprint::is_deposit_allowed(&resource_address, api)?;
1400        if is_deposit_allowed {
1401            AccountBlueprint::deposit(bucket, api)?;
1402            Ok(None)
1403        } else if let Some(badge) = authorized_depositor_badge {
1404            match AccountBlueprint::validate_badge_is_authorized_depositor(&badge, api)? {
1405                // The passed authorized depositor badge is indeed an authorized depositor.
1406                Ok(_) => {}
1407                // The badge that they claim to be an authorized depositor is not one. Return the
1408                // resources back to them.
1409                Err(AccountError::NotAnAuthorizedDepositor { .. }) => {
1410                    let event = if resource_address.is_fungible() {
1411                        RejectedDepositEvent::Fungible(resource_address, bucket.amount(api)?)
1412                    } else {
1413                        RejectedDepositEvent::NonFungible(
1414                            resource_address,
1415                            bucket.non_fungible_local_ids(api)?,
1416                        )
1417                    };
1418                    Runtime::emit_event(api, event)?;
1419                    return Ok(Some(bucket));
1420                }
1421                // Some other account error is encountered - impossible case since the function
1422                // will not return it. In either way, we propagate it.
1423                Err(error) => return Err(error.into()),
1424            }
1425            AccountBlueprint::validate_badge_is_present(badge, api)?;
1426            AccountBlueprint::deposit(bucket, api)?;
1427            Ok(None)
1428        } else {
1429            let event = if resource_address.is_fungible() {
1430                RejectedDepositEvent::Fungible(resource_address, bucket.amount(api)?)
1431            } else {
1432                RejectedDepositEvent::NonFungible(
1433                    resource_address,
1434                    bucket.non_fungible_local_ids(api)?,
1435                )
1436            };
1437            Runtime::emit_event(api, event)?;
1438            Ok(Some(bucket))
1439        }
1440    }
1441
1442    pub fn try_deposit_batch_or_refund<Y: SystemApi<RuntimeError>>(
1443        buckets: Vec<Bucket>,
1444        authorized_depositor_badge: Option<ResourceOrNonFungible>,
1445        api: &mut Y,
1446    ) -> Result<Option<Vec<Bucket>>, RuntimeError> {
1447        let offending_buckets = buckets
1448            .iter()
1449            .map(|bucket| {
1450                bucket
1451                    .resource_address(api)
1452                    .and_then(|resource_address| {
1453                        AccountBlueprint::is_deposit_allowed(&resource_address, api)
1454                    })
1455                    .map(|can_be_deposited| (bucket, can_be_deposited))
1456            })
1457            .collect::<Result<Vec<_>, _>>()?
1458            .into_iter()
1459            .filter_map(|(bucket, can_be_deposited)| {
1460                if !can_be_deposited {
1461                    Some(Bucket(bucket.0))
1462                } else {
1463                    None
1464                }
1465            })
1466            .collect::<Vec<_>>();
1467
1468        if offending_buckets.is_empty() {
1469            AccountBlueprint::deposit_batch(buckets, api)?;
1470            Ok(None)
1471        } else if let Some(badge) = authorized_depositor_badge {
1472            match AccountBlueprint::validate_badge_is_authorized_depositor(&badge, api)? {
1473                // The passed authorized depositor badge is indeed an authorized depositor.
1474                Ok(_) => {}
1475                // The badge that they claim to be an authorized depositor is not one. Return the
1476                // resources back to them.
1477                Err(AccountError::NotAnAuthorizedDepositor { .. }) => {
1478                    for bucket in offending_buckets {
1479                        let resource_address = bucket.resource_address(api)?;
1480                        let event = if resource_address.is_fungible() {
1481                            RejectedDepositEvent::Fungible(resource_address, bucket.amount(api)?)
1482                        } else {
1483                            RejectedDepositEvent::NonFungible(
1484                                resource_address,
1485                                bucket.non_fungible_local_ids(api)?,
1486                            )
1487                        };
1488                        Runtime::emit_event(api, event)?;
1489                    }
1490                    return Ok(Some(buckets));
1491                }
1492                // Some other account error is encountered - impossible case since the function
1493                // will not return it. In either way, we propagate it.
1494                Err(error) => return Err(error.into()),
1495            }
1496            AccountBlueprint::validate_badge_is_present(badge, api)?;
1497            AccountBlueprint::deposit_batch(buckets, api)?;
1498            Ok(None)
1499        } else {
1500            for bucket in offending_buckets {
1501                let resource_address = bucket.resource_address(api)?;
1502                let event = if resource_address.is_fungible() {
1503                    RejectedDepositEvent::Fungible(resource_address, bucket.amount(api)?)
1504                } else {
1505                    RejectedDepositEvent::NonFungible(
1506                        resource_address,
1507                        bucket.non_fungible_local_ids(api)?,
1508                    )
1509                };
1510                Runtime::emit_event(api, event)?;
1511            }
1512            Ok(Some(buckets))
1513        }
1514    }
1515}
1516
1517pub struct AccountBlueprintCuttlefishExtension;
1518
1519impl AccountBlueprintCuttlefishExtension {
1520    pub fn added_functions_schema() -> (
1521        IndexMap<String, FunctionSchemaInit>,
1522        VersionedSchema<ScryptoCustomSchema>,
1523    ) {
1524        let mut aggregator = TypeAggregator::<ScryptoCustomTypeKind>::new();
1525        let mut functions = index_map_new();
1526        functions.insert(
1527            ACCOUNT_BALANCE_IDENT.to_string(),
1528            FunctionSchemaInit {
1529                receiver: Some(ReceiverInfo::normal_ref()),
1530                input: TypeRef::Static(
1531                    aggregator.add_child_type_and_descendents::<AccountBalanceInput>(),
1532                ),
1533                output: TypeRef::Static(
1534                    aggregator.add_child_type_and_descendents::<AccountBalanceOutput>(),
1535                ),
1536                export: ACCOUNT_BALANCE_IDENT.to_string(),
1537            },
1538        );
1539
1540        functions.insert(
1541            ACCOUNT_NON_FUNGIBLE_LOCAL_IDS_IDENT.to_string(),
1542            FunctionSchemaInit {
1543                receiver: Some(ReceiverInfo::normal_ref()),
1544                input: TypeRef::Static(
1545                    aggregator.add_child_type_and_descendents::<AccountNonFungibleLocalIdsInput>(),
1546                ),
1547                output: TypeRef::Static(
1548                    aggregator.add_child_type_and_descendents::<AccountNonFungibleLocalIdsOutput>(),
1549                ),
1550                export: ACCOUNT_NON_FUNGIBLE_LOCAL_IDS_IDENT.to_string(),
1551            },
1552        );
1553
1554        functions.insert(
1555            ACCOUNT_HAS_NON_FUNGIBLE_IDENT.to_string(),
1556            FunctionSchemaInit {
1557                receiver: Some(ReceiverInfo::normal_ref()),
1558                input: TypeRef::Static(
1559                    aggregator.add_child_type_and_descendents::<AccountHasNonFungibleInput>(),
1560                ),
1561                output: TypeRef::Static(
1562                    aggregator.add_child_type_and_descendents::<AccountHasNonFungibleOutput>(),
1563                ),
1564                export: ACCOUNT_HAS_NON_FUNGIBLE_IDENT.to_string(),
1565            },
1566        );
1567        let schema = generate_full_schema(aggregator);
1568        (functions, schema)
1569    }
1570
1571    pub fn invoke_export<Y: SystemApi<RuntimeError>>(
1572        export_name: &str,
1573        input: &IndexedScryptoValue,
1574        api: &mut Y,
1575    ) -> Result<IndexedScryptoValue, RuntimeError> {
1576        match export_name {
1577            ACCOUNT_BALANCE_IDENT => {
1578                let AccountBalanceInput { resource_address } = input.as_typed().map_err(|e| {
1579                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
1580                })?;
1581
1582                let rtn = Self::balance(resource_address, api)?;
1583                Ok(IndexedScryptoValue::from_typed(&rtn))
1584            }
1585            ACCOUNT_NON_FUNGIBLE_LOCAL_IDS_IDENT => {
1586                let AccountNonFungibleLocalIdsInput {
1587                    resource_address,
1588                    limit,
1589                } = input.as_typed().map_err(|e| {
1590                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
1591                })?;
1592
1593                let rtn = Self::non_fungible_local_ids(resource_address, limit, api)?;
1594                Ok(IndexedScryptoValue::from_typed(&rtn))
1595            }
1596            ACCOUNT_HAS_NON_FUNGIBLE_IDENT => {
1597                let AccountHasNonFungibleInput {
1598                    resource_address,
1599                    local_id,
1600                } = input.as_typed().map_err(|e| {
1601                    RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
1602                })?;
1603
1604                let rtn = Self::has_non_fungible(resource_address, local_id, api)?;
1605                Ok(IndexedScryptoValue::from_typed(&rtn))
1606            }
1607            _ => Err(RuntimeError::ApplicationError(
1608                ApplicationError::ExportDoesNotExist(export_name.to_string()),
1609            )),
1610        }
1611    }
1612
1613    pub fn balance<Y: SystemApi<RuntimeError>>(
1614        resource_address: ResourceAddress,
1615        api: &mut Y,
1616    ) -> Result<Decimal, RuntimeError> {
1617        match AccountBlueprint::get_vault(
1618            resource_address,
1619            |vault, api| vault.amount(api),
1620            false,
1621            api,
1622        ) {
1623            Ok(balance) => Ok(balance),
1624            Err(RuntimeError::ApplicationError(ApplicationError::AccountError(
1625                AccountError::VaultDoesNotExist { .. },
1626            ))) => Ok(Decimal::ZERO),
1627            Err(error) => Err(error),
1628        }
1629    }
1630
1631    pub fn non_fungible_local_ids<Y: SystemApi<RuntimeError>>(
1632        resource_address: ResourceAddress,
1633        limit: u32,
1634        api: &mut Y,
1635    ) -> Result<IndexSet<NonFungibleLocalId>, RuntimeError> {
1636        match AccountBlueprint::get_vault(
1637            resource_address,
1638            |vault, api| vault.non_fungible_local_ids(limit, api),
1639            false,
1640            api,
1641        ) {
1642            Ok(ids) => Ok(ids),
1643            Err(RuntimeError::ApplicationError(ApplicationError::AccountError(
1644                AccountError::VaultDoesNotExist { .. },
1645            ))) => Ok(Default::default()),
1646            Err(error) => Err(error),
1647        }
1648    }
1649
1650    pub fn has_non_fungible<Y: SystemApi<RuntimeError>>(
1651        resource_address: ResourceAddress,
1652        local_id: NonFungibleLocalId,
1653        api: &mut Y,
1654    ) -> Result<bool, RuntimeError> {
1655        match AccountBlueprint::get_vault(
1656            resource_address,
1657            |vault, api| vault.contains_non_fungible(local_id, api),
1658            false,
1659            api,
1660        ) {
1661            Ok(result) => Ok(result),
1662            Err(RuntimeError::ApplicationError(ApplicationError::AccountError(
1663                AccountError::VaultDoesNotExist { .. },
1664            ))) => Ok(false),
1665            Err(error) => Err(error),
1666        }
1667    }
1668}