Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/target
Cargo.lock
near-plugins-derive/tests/contracts/*/target
near-plugins/tests/contracts/*/target
Comment thread
mooori marked this conversation as resolved.
Outdated
examples/target

# Ignore IDE data
Expand Down
121 changes: 78 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,
Comment thread
mooori marked this conversation as resolved.
}

/// Generates a prefix by concatenating the input parameters.
Expand All @@ -100,6 +95,34 @@ 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_or_init(&self) -> #acl_type {
Comment thread
karim-en marked this conversation as resolved.
Outdated
self.acl_get_storage().unwrap_or_else(|| self.acl_init_storage_unchecked())
}

fn acl_init_storage_unchecked(&self) -> #acl_type {
Comment thread
karim-en marked this conversation as resolved.
Outdated
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(),
);
acl_storage
}
}

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 @@ -474,6 +497,39 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
}
}

fn get_default_permissioned_accounts() -> #cratename::access_controllable::PermissionedAccounts {
let roles = <#role_type>::acl_role_variants();
let mut map = ::std::collections::HashMap::new();
for role in roles {
let role: #role_type = ::std::convert::TryFrom::try_from(role)
.unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));

map.insert(
role.into(),
#cratename::access_controllable::PermissionedAccountsPerRole {
admins: Default::default(),
grantees: Default::default(),
}
);
}

#cratename::access_controllable::PermissionedAccounts {
super_admins: Default::default(),
roles: map,
}
}

macro_rules! return_if_none {
($res:expr, $default_value:expr) => {
match $res {
Some(val) => val,
None => {
return $default_value;
}
}
};
}

// Note that `#[near-bindgen]` exposes non-public functions in trait
// implementations. This is [documented] behavior. Therefore some
// functions are made `#[private]` despite _not_ being public.
Expand All @@ -487,56 +543,56 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream

#[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_or_init().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)
return_if_none!(self.acl_get_storage(), false).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_or_init().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)
return_if_none!(self.acl_get_storage(), false).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_or_init().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_or_init().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_or_init().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_or_init().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_or_init().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)
return_if_none!(self.acl_get_storage(), false).has_role(role, &account_id)
}

fn acl_has_any_role(&self, roles: Vec<String>, account_id: ::near_sdk::AccountId) -> bool {
Expand All @@ -546,61 +602,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)
return_if_none!(self.acl_get_storage(), false).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)
return_if_none!(self.acl_get_storage(), vec![]).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)
return_if_none!(self.acl_get_storage(), vec![]).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)
return_if_none!(self.acl_get_storage(), vec![]).get_bearers(permission, skip, limit)
}

fn acl_get_permissioned_accounts(&self) -> #cratename::access_controllable::PermissionedAccounts {
self.#acl_field.get_permissioned_accounts()
return_if_none!(self.acl_get_storage(), get_default_permissioned_accounts()).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
27 changes: 12 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,7 @@ 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 };

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 +64,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_or_init().add_admin_unchecked(role, account_id);
// contract.acl_get_or_init().grant_role_unchecked(role, account_id);
// ```
//
// **Attention**: for security reasons, `__acl.*_unchecked` methods should only be called
// **Attention**: for security reasons, `acl_get_or_init().*_unchecked` methods should only be called
// from within methods with attribute `#[init]` or `#[private]`.
}

Expand Down Expand Up @@ -136,7 +132,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_or_init().add_super_admin_unchecked(&account_id)
}
}

Expand All @@ -145,31 +141,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_or_init().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_or_init().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_or_init()
.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_or_init().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_or_init().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_or_init().grant_role_unchecked(role, &account_id)
}
}
1 change: 0 additions & 1 deletion near-plugins-derive/tests/contracts/pausable/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ impl Counter {
pub fn new(pause_manager: AccountId) -> Self {
let mut contract = Self {
counter: 0,
__acl: Default::default(),
};

// Make the contract itself super admin. This allows us to grant any role in the
Expand Down