Managed Access Example

This example demonstrates the use of the FlatAdmin blueprint to manage access in another blueprint.

You can find the code for the FlatAdmin blueprint here and the code for the ManagedAccess blueprint here.

Note that in order for this example to function, you will have to publish the package containing the FlatAdmin blueprint to your simulator to the given address once it’s been published (or change the address imported near the top of lib.rs in this package).

If you wish to publish FlatAdmin to the appropriate address, switch to that directory and run:

resim publish . --package-address package_sim1q9h0dr0z36zaq6h66lg5putxtztyf0sgelxu654r67ks765aue

Importing & Calling a Blueprint

Currently, importing another blueprint requires a few manual steps. We expect to simplify this process in the future, but for now here are the steps:

  1. Publish the package containing the blueprint you wish to import.

  2. Import its functions using the extern_blueprint! macro and methods using the macro. Example:

use scrypto::prelude::*;

extern_blueprint!(
    "package_sim1p4kwg8fa7ldhwh8exe5w4acjhp9v982svmxp3yqa8ncruad4rv980g",
    FlatAdmin {
        fn instantiate_flat_admin(badge_name: String) -> (Global<FlatAdmin>, Bucket);
        fn create_additional_admin(&mut self) -> Bucket;
        fn destroy_admin_badge(&mut self, to_destroy: Bucket);
        fn get_admin_badge_address(&self) -> ResourceAddress;
    }
);

Now you’ll be able to call functions on that blueprint like so: Blueprint::<FlatAdmin>::some_function(args)

Resources and Data

struct ManagedAccess {
  admin_badge: ResourceAddress,
  flat_admin_controller: Global<FlatAdmin>,
  protected_vault: Vault,
}

Our instantiated component will maintain a single vault which stores XRD. Anyone may deposit to the vault, but only a caller in possession of an admin badge may withdraw from it.

The only state we need to maintain is the aforementioned Vault, and the ResourceAddress of the badge used for authorization. As a convenience for the user, we will also store the Global<FlatAdmin> object of the FlatAdmin component which manages the supply of those badges.

Getting Ready for Instantiation

In order to instantiate the ManagedAccess component, we’ll require a parameter that represent the FlatAdmin package address and return to the caller a tuple containing the newly instantiated component address, and a bucket containing the first admin badge created by our FlatAdmin badge manager:

pub fn instantiate_managed_access(badge_name: String) -> (Global<FlatAdmin>, Bucket)

We first need to set up our component auth such that the only the admin role may withdraw funds from a managed access component.

enable_method_auth! {
    roles {
        admin => updatable_by: [];
    },
    methods {
        withdraw_all => restrict_to: [admin];
        deposit => PUBLIC;
        get_admin_badge_address => PUBLIC;
        get_flat_admin_controller_address => PUBLIC;
    }
}

We’ll then need to instantiate a FlatAdmin component within our ManagedAccess instantiation function, and store the Global<FlatAdmin> and the badge we expect to receive.

let
(flat_admin_component, admin_badge): (Global<FlatAdmin>, Bucket) =
Blueprint::<FlatAdmin>::instantiate_flat_admin(badge_name);

That gives us everything we need to populate our component state, instantiate it, and return the results to our caller. But we also need to finish configuring our component auth by mapping the role we defined within the enable_method_auth! to an AccessRule:

let component = Self {
    admin_badge: admin_badge.resource_address(),
    flat_admin_controller: flat_admin_component,
    protected_vault: Vault::new(RADIX_TOKEN),
}
.instantiate()
.prepare_to_globalize(OwnerRole::None) (1)
.roles(
    roles!(
        admin => rule!(require(admin_badge.resource_address())); (2)
    )
)
.globalize();

(component, admin_badge)
1 Since the badge we received from FlatAdmin is not meant to control our ManagedAccess component, we will set the OwnerRole to None.
2 We map the AccessRule for the admin role to the admin badge we received from FlatAdmin.

Adding Methods

First, we’ll then create a protected method to allow withdrawal only for the admin role which we specified within enable_method_auth! macro. This means that only the admin role can access this method and the admin role must have the admin badge received from the FlatAdmin component before this method can be called. The Radix Engine will automatically review the permissions required and whether it has been satisfied.

pub fn withdraw_all(&mut self) -> Bucket {
  self.protected_vault.take_all()
}

The rest of the methods are straightforward. We’ll add a method to permit anyone to deposit XRD, and then some read-only methods to return data about our admin badge and the FlatAdmin controller which manages the supply of badges.

pub fn deposit(&mut self, to_deposit: Bucket) {
  self.protected_vault.put(to_deposit);
}

pub fn get_admin_badge_address(&self) -> ResourceAddress {
    self.admin_badge
}

pub fn get_flat_admin_controller_address(&self) -> ComponentAddress {
    self.flat_admin_controller
}

That’s it. Access control components like FlatAdmin are expected to be very commonly consumed by other blueprints, as they provide consistent, re-usable mechanisms to manage privileges.

Trying this example with resim

Let’s deploy, instantiate, and call methods on our component

  1. Create a new account, and save the account address:

    resim new-account
  2. Publish the FlatAdmin and ManagedAccess packages:

    cd core/flat-admin/
    resim publish .
    resim publish ../managed-access/
  3. Call the instantiate_managed_access function to instantiate a ManagedAccess component. Save the second returned component address and second resource address (which is the admin badge):

    resim call-function <MANAGED_ACCESS_PACKAGE_ADDRESS> ManagedAccess instantiate_managed_access [flat_admin_package_address]
  4. Call the deposit method of the component we just instantiated:

    resim call-method <COMPONENT_ADDRESS> deposit resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3:1000
  5. Look at the resources in your account to get the address of the admin badge:

    resim show <ACCOUNT_ADDRESS>
  6. Call the withdraw_all method by running this command:

    resim call-method <COMPONENT_ADDRESS> withdraw_all --proofs <ADMIN_BADGE_ADDRESS>:1