Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ACL: Remove __acl field #84

Merged
merged 9 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Cargo.lock
near-plugins-derive/tests/contracts/*/target
examples/target

**/target/**
# Ignore IDE data
.vscode/
.idea/
92 changes: 49 additions & 43 deletions near-plugins-derive/src/access_controllable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use darling::FromMeta;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::parse::Parser;
use syn::{parse_macro_input, AttributeArgs, ItemFn, ItemStruct};

/// Defines attributes for the `access_controllable` macro.
Expand All @@ -17,7 +16,6 @@ pub struct MacroArgs {
}

const DEFAULT_STORAGE_PREFIX: &str = "__acl";
const DEFAULT_ACL_FIELD_NAME: &str = "__acl";
const DEFAULT_ACL_TYPE_NAME: &str = "__Acl";

const ERR_PARSE_BITFLAG: &str = "Value does not correspond to a permission";
Expand All @@ -27,13 +25,9 @@ const ERR_PARSE_ROLE: &str = "Value does not correspond to a role";
pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream {
let cratename = cratename();
let attr_args = parse_macro_input!(attrs as AttributeArgs);
let mut input: ItemStruct = parse_macro_input!(item);
let acl_field = syn::Ident::new(DEFAULT_ACL_FIELD_NAME, Span::call_site());
let input: ItemStruct = parse_macro_input!(item);
let acl_type = syn::Ident::new(DEFAULT_ACL_TYPE_NAME, Span::call_site());
let bitflags_type = new_bitflags_type_ident(Span::call_site());
if let Err(e) = inject_acl_field(&mut input, &acl_field, &acl_type) {
return TokenStream::from(e.to_compile_error());
}
let ItemStruct { ident, .. } = input.clone();

let macro_args = match MacroArgs::from_list(&attr_args) {
Expand Down Expand Up @@ -90,6 +84,7 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
Permissions,
Bearers,
BearersSet { permission: #bitflags_type },
AclStorage,
}

/// Generates a prefix by concatenating the input parameters.
Expand All @@ -100,6 +95,33 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
[base, specifier.as_slice()].concat()
}

impl #ident {
fn acl_get_storage(&self) -> Option<#acl_type> {
let base_prefix = <#ident as AccessControllable>::acl_storage_prefix();
near_sdk::env::storage_read(&__acl_storage_prefix(
base_prefix,
__AclStorageKey::AclStorage,
))
.map(|acl_storage_bytes| {
#acl_type::try_from_slice(&acl_storage_bytes)
.unwrap_or_else(|_| near_sdk::env::panic_str("ACL: invalid acl storage format"))
})
}

fn acl_get(&self) -> #acl_type {
self.acl_get_storage().unwrap_or_else(|| ::near_sdk::env::panic_str("ACL: storage isn't initialized"))
}

fn acl_init_storage_unchecked(&self) {
let base_prefix = <#ident as AccessControllable>::acl_storage_prefix();
let acl_storage: #acl_type = Default::default();
near_sdk::env::storage_write(
&__acl_storage_prefix(base_prefix, __AclStorageKey::AclStorage),
&acl_storage.try_to_vec().unwrap(),
);
}
}

impl #acl_type {
fn new_bearers_set(permission: #bitflags_type) -> ::near_sdk::store::UnorderedSet<::near_sdk::AccountId> {
let base_prefix = <#ident as AccessControllable>::acl_storage_prefix();
Expand Down Expand Up @@ -485,58 +507,63 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
(#storage_prefix).as_bytes()
}

fn acl_init_storage(&self) {
::near_sdk::require!(self.acl_get_storage().is_none(), "ACL: storage was already initialized");
self.acl_init_storage_unchecked();
}

#[private]
fn acl_init_super_admin(&mut self, account_id: ::near_sdk::AccountId) -> bool {
self.#acl_field.init_super_admin(&account_id)
self.acl_get().init_super_admin(&account_id)
}

fn acl_role_variants(&self) -> Vec<&'static str> {
<#role_type>::acl_role_variants()
}

fn acl_is_super_admin(&self, account_id: ::near_sdk::AccountId) -> bool {
self.#acl_field.is_super_admin(&account_id)
self.acl_get().is_super_admin(&account_id)
}

fn acl_add_admin(&mut self, role: String, account_id: ::near_sdk::AccountId) -> Option<bool> {
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
self.#acl_field.add_admin(role, &account_id)
self.acl_get().add_admin(role, &account_id)
}

fn acl_is_admin(&self, role: String, account_id: ::near_sdk::AccountId) -> bool {
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
self.#acl_field.is_admin(role, &account_id)
self.acl_get().is_admin(role, &account_id)
}

fn acl_revoke_admin(&mut self, role: String, account_id: ::near_sdk::AccountId) -> Option<bool> {
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
self.#acl_field.revoke_admin(role, &account_id)
self.acl_get().revoke_admin(role, &account_id)
}

fn acl_renounce_admin(&mut self, role: String) -> bool {
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
self.#acl_field.renounce_admin(role)
self.acl_get().renounce_admin(role)
}

fn acl_revoke_role(&mut self, role: String, account_id: ::near_sdk::AccountId) -> Option<bool> {
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
self.#acl_field.revoke_role(role, &account_id)
self.acl_get().revoke_role(role, &account_id)
}

fn acl_renounce_role(&mut self, role: String) -> bool {
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
self.#acl_field.renounce_role(role)
self.acl_get().renounce_role(role)
}

fn acl_grant_role(&mut self, role: String, account_id: ::near_sdk::AccountId) -> Option<bool> {
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
self.#acl_field.grant_role(role, &account_id)
self.acl_get().grant_role(role, &account_id)
}


fn acl_has_role(&self, role: String, account_id: ::near_sdk::AccountId) -> bool {
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
self.#acl_field.has_role(role, &account_id)
self.acl_get().has_role(role, &account_id)
}

fn acl_has_any_role(&self, roles: Vec<String>, account_id: ::near_sdk::AccountId) -> bool {
Expand All @@ -546,61 +573,40 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE))
})
.collect();
self.#acl_field.has_any_role(roles, &account_id)
self.acl_get().has_any_role(roles, &account_id)
}

fn acl_get_super_admins(&self, skip: u64, limit: u64) -> Vec<::near_sdk::AccountId> {
let permission = <#bitflags_type>::from_bits(
<#role_type>::acl_super_admin_permission()
)
.unwrap_or_else(|| ::near_sdk::env::panic_str(#ERR_PARSE_BITFLAG));
self.#acl_field.get_bearers(permission, skip, limit)
self.acl_get().get_bearers(permission, skip, limit)
}

fn acl_get_admins(&self, role: String, skip: u64, limit: u64) -> Vec<::near_sdk::AccountId> {
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
let permission = <#bitflags_type>::from_bits(role.acl_admin_permission())
.unwrap_or_else(|| ::near_sdk::env::panic_str(#ERR_PARSE_BITFLAG));
self.#acl_field.get_bearers(permission, skip, limit)
self.acl_get().get_bearers(permission, skip, limit)
}

fn acl_get_grantees(&self, role: String, skip: u64, limit: u64) -> Vec<::near_sdk::AccountId> {
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
let permission = <#bitflags_type>::from_bits(role.acl_permission())
.unwrap_or_else(|| ::near_sdk::env::panic_str(#ERR_PARSE_BITFLAG));
self.#acl_field.get_bearers(permission, skip, limit)
self.acl_get().get_bearers(permission, skip, limit)
}

fn acl_get_permissioned_accounts(&self) -> #cratename::access_controllable::PermissionedAccounts {
self.#acl_field.get_permissioned_accounts()
self.acl_get().get_permissioned_accounts()
}
}
};

output.into()
}

fn inject_acl_field(
item: &mut ItemStruct,
field_name: &syn::Ident,
acl_type: &syn::Ident,
) -> Result<(), syn::Error> {
let fields = match item.fields {
syn::Fields::Named(ref mut fields) => fields,
_ => {
return Err(syn::Error::new(
item.ident.span(),
"Expected to have named fields",
))
}
};

fields.named.push(syn::Field::parse_named.parse2(quote! {
#field_name: #acl_type
})?);
Ok(())
}

/// Defines attributes for the `access_control_any` macro.
#[derive(Debug, FromMeta)]
pub struct MacroArgsAny {
Expand Down
28 changes: 13 additions & 15 deletions near-plugins-derive/tests/contracts/access_controllable/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,8 @@ impl Counter {
/// identifier of the enum variant, i.e. `"Updater"` for `Role::Updater`.
#[init]
pub fn new(admins: HashMap<String, AccountId>, grantees: HashMap<String, AccountId>) -> Self {
let mut contract = Self {
counter: 0,
// Initialize `AccessControllable` plugin state.
__acl: Default::default(),
};
let mut contract = Self { counter: 0 };
contract.acl_init_storage();

if admins.len() > 0 || grantees.len() > 0 {
// First we make the contract itself super admin to allow it adding admin and grantees.
Expand All @@ -68,11 +65,11 @@ impl Counter {
// granting roles, for example:
//
// ```
// contract.__acl.add_admin_unchecked(role, account_id);
// contract.__acl.grant_role_unchecked(role, account_id);
// contract.acl_get().add_admin_unchecked(role, account_id);
// contract.acl_get().grant_role_unchecked(role, account_id);
// ```
//
// **Attention**: for security reasons, `__acl.*_unchecked` methods should only be called
// **Attention**: for security reasons, `acl_get().*_unchecked` methods should only be called
// from within methods with attribute `#[init]` or `#[private]`.
}

Expand Down Expand Up @@ -136,7 +133,7 @@ impl Counter {
self.acl_is_super_admin(env::predecessor_account_id()),
"Only super admins are allowed to add other super admins."
);
self.__acl.add_super_admin_unchecked(&account_id)
self.acl_get().add_super_admin_unchecked(&account_id)
}
}

Expand All @@ -145,31 +142,32 @@ impl Counter {
impl Counter {
#[private]
pub fn acl_add_super_admin_unchecked(&mut self, account_id: AccountId) -> bool {
self.__acl.add_super_admin_unchecked(&account_id)
self.acl_get().add_super_admin_unchecked(&account_id)
}

#[private]
pub fn acl_revoke_super_admin_unchecked(&mut self, account_id: AccountId) -> bool {
self.__acl.revoke_super_admin_unchecked(&account_id)
self.acl_get().revoke_super_admin_unchecked(&account_id)
}

#[private]
pub fn acl_revoke_role_unchecked(&mut self, role: Role, account_id: AccountId) -> bool {
self.__acl.revoke_role_unchecked(role.into(), &account_id)
self.acl_get()
.revoke_role_unchecked(role.into(), &account_id)
}

#[private]
pub fn acl_add_admin_unchecked(&mut self, role: Role, account_id: AccountId) -> bool {
self.__acl.add_admin_unchecked(role, &account_id)
self.acl_get().add_admin_unchecked(role, &account_id)
}

#[private]
pub fn acl_revoke_admin_unchecked(&mut self, role: Role, account_id: AccountId) -> bool {
self.__acl.revoke_admin_unchecked(role, &account_id)
self.acl_get().revoke_admin_unchecked(role, &account_id)
}

#[private]
pub fn acl_grant_role_unchecked(&mut self, role: Role, account_id: AccountId) -> bool {
self.__acl.grant_role_unchecked(role, &account_id)
self.acl_get().grant_role_unchecked(role, &account_id)
}
}
2 changes: 1 addition & 1 deletion near-plugins-derive/tests/contracts/pausable/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ impl Counter {
pub fn new(pause_manager: AccountId) -> Self {
let mut contract = Self {
counter: 0,
__acl: Default::default(),
};
contract.acl_init_storage();

// Make the contract itself super admin. This allows us to grant any role in the
// constructor.
Expand Down
3 changes: 3 additions & 0 deletions near-plugins/src/access_controllable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ pub trait AccessControllable {
/// ```
fn acl_storage_prefix() -> &'static [u8];

/// Initialize the access control storage.
fn acl_init_storage(&self);

/// Returns the names of all variants of the enum that represents roles.
///
/// In the default implementation provided by this crate, this enum is defined by contract
Expand Down