Skip to content

Commit 860ec9d

Browse files
karim-enmooori
andauthored
feat(AccessControllable)!: Remove __acl field (#84)
* Remove acl field * Rename acl storage key * Apply cargo fmt * Rename `acl_get_field` * Change gitignore paths * ACL: don't panic if acl isn't initialized * Apply suggestions from code review Co-authored-by: mooori <moritz.zielke@aurora.dev> * Revert gitignore changes * Fix comment --------- Co-authored-by: mooori <moritz.zielke@aurora.dev>
1 parent fec96ab commit 860ec9d

File tree

3 files changed

+91
-60
lines changed

3 files changed

+91
-60
lines changed

near-plugins-derive/src/access_controllable.rs

+78-43
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use darling::FromMeta;
55
use proc_macro::TokenStream;
66
use proc_macro2::Span;
77
use quote::quote;
8-
use syn::parse::Parser;
98
use syn::{parse_macro_input, AttributeArgs, ItemFn, ItemStruct};
109

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

1918
const DEFAULT_STORAGE_PREFIX: &str = "__acl";
20-
const DEFAULT_ACL_FIELD_NAME: &str = "__acl";
2119
const DEFAULT_ACL_TYPE_NAME: &str = "__Acl";
2220

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

3933
let macro_args = match MacroArgs::from_list(&attr_args) {
@@ -90,6 +84,7 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
9084
Permissions,
9185
Bearers,
9286
BearersSet { permission: #bitflags_type },
87+
AclStorage,
9388
}
9489

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

98+
impl #ident {
99+
fn acl_get_storage(&self) -> Option<#acl_type> {
100+
let base_prefix = <#ident as AccessControllable>::acl_storage_prefix();
101+
near_sdk::env::storage_read(&__acl_storage_prefix(
102+
base_prefix,
103+
__AclStorageKey::AclStorage,
104+
))
105+
.map(|acl_storage_bytes| {
106+
#acl_type::try_from_slice(&acl_storage_bytes)
107+
.unwrap_or_else(|_| near_sdk::env::panic_str("ACL: invalid acl storage format"))
108+
})
109+
}
110+
111+
fn acl_get_or_init(&mut self) -> #acl_type {
112+
self.acl_get_storage().unwrap_or_else(|| self.acl_init_storage_unchecked())
113+
}
114+
115+
fn acl_init_storage_unchecked(&mut self) -> #acl_type {
116+
let base_prefix = <#ident as AccessControllable>::acl_storage_prefix();
117+
let acl_storage: #acl_type = Default::default();
118+
near_sdk::env::storage_write(
119+
&__acl_storage_prefix(base_prefix, __AclStorageKey::AclStorage),
120+
&acl_storage.try_to_vec().unwrap(),
121+
);
122+
acl_storage
123+
}
124+
}
125+
103126
impl #acl_type {
104127
fn new_bearers_set(permission: #bitflags_type) -> ::near_sdk::store::UnorderedSet<::near_sdk::AccountId> {
105128
let base_prefix = <#ident as AccessControllable>::acl_storage_prefix();
@@ -474,6 +497,39 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
474497
}
475498
}
476499

500+
fn get_default_permissioned_accounts() -> #cratename::access_controllable::PermissionedAccounts {
501+
let roles = <#role_type>::acl_role_variants();
502+
let mut map = ::std::collections::HashMap::new();
503+
for role in roles {
504+
let role: #role_type = ::std::convert::TryFrom::try_from(role)
505+
.unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
506+
507+
map.insert(
508+
role.into(),
509+
#cratename::access_controllable::PermissionedAccountsPerRole {
510+
admins: Default::default(),
511+
grantees: Default::default(),
512+
}
513+
);
514+
}
515+
516+
#cratename::access_controllable::PermissionedAccounts {
517+
super_admins: Default::default(),
518+
roles: map,
519+
}
520+
}
521+
522+
macro_rules! return_if_none {
523+
($res:expr, $default_value:expr) => {
524+
match $res {
525+
Some(val) => val,
526+
None => {
527+
return $default_value;
528+
}
529+
}
530+
};
531+
}
532+
477533
// Note that `#[near-bindgen]` exposes non-public functions in trait
478534
// implementations. This is [documented] behavior. Therefore some
479535
// functions are made `#[private]` despite _not_ being public.
@@ -487,56 +543,56 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
487543

488544
#[private]
489545
fn acl_init_super_admin(&mut self, account_id: ::near_sdk::AccountId) -> bool {
490-
self.#acl_field.init_super_admin(&account_id)
546+
self.acl_get_or_init().init_super_admin(&account_id)
491547
}
492548

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

497553
fn acl_is_super_admin(&self, account_id: ::near_sdk::AccountId) -> bool {
498-
self.#acl_field.is_super_admin(&account_id)
554+
return_if_none!(self.acl_get_storage(), false).is_super_admin(&account_id)
499555
}
500556

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

506562
fn acl_is_admin(&self, role: String, account_id: ::near_sdk::AccountId) -> bool {
507563
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
508-
self.#acl_field.is_admin(role, &account_id)
564+
return_if_none!(self.acl_get_storage(), false).is_admin(role, &account_id)
509565
}
510566

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

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

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

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

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

536592

537593
fn acl_has_role(&self, role: String, account_id: ::near_sdk::AccountId) -> bool {
538594
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
539-
self.#acl_field.has_role(role, &account_id)
595+
return_if_none!(self.acl_get_storage(), false).has_role(role, &account_id)
540596
}
541597

542598
fn acl_has_any_role(&self, roles: Vec<String>, account_id: ::near_sdk::AccountId) -> bool {
@@ -546,61 +602,40 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
546602
::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE))
547603
})
548604
.collect();
549-
self.#acl_field.has_any_role(roles, &account_id)
605+
return_if_none!(self.acl_get_storage(), false).has_any_role(roles, &account_id)
550606
}
551607

552608
fn acl_get_super_admins(&self, skip: u64, limit: u64) -> Vec<::near_sdk::AccountId> {
553609
let permission = <#bitflags_type>::from_bits(
554610
<#role_type>::acl_super_admin_permission()
555611
)
556612
.unwrap_or_else(|| ::near_sdk::env::panic_str(#ERR_PARSE_BITFLAG));
557-
self.#acl_field.get_bearers(permission, skip, limit)
613+
return_if_none!(self.acl_get_storage(), vec![]).get_bearers(permission, skip, limit)
558614
}
559615

560616
fn acl_get_admins(&self, role: String, skip: u64, limit: u64) -> Vec<::near_sdk::AccountId> {
561617
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
562618
let permission = <#bitflags_type>::from_bits(role.acl_admin_permission())
563619
.unwrap_or_else(|| ::near_sdk::env::panic_str(#ERR_PARSE_BITFLAG));
564-
self.#acl_field.get_bearers(permission, skip, limit)
620+
return_if_none!(self.acl_get_storage(), vec![]).get_bearers(permission, skip, limit)
565621
}
566622

567623
fn acl_get_grantees(&self, role: String, skip: u64, limit: u64) -> Vec<::near_sdk::AccountId> {
568624
let role: #role_type = ::std::convert::TryFrom::try_from(role.as_str()).unwrap_or_else(|_| ::near_sdk::env::panic_str(#ERR_PARSE_ROLE));
569625
let permission = <#bitflags_type>::from_bits(role.acl_permission())
570626
.unwrap_or_else(|| ::near_sdk::env::panic_str(#ERR_PARSE_BITFLAG));
571-
self.#acl_field.get_bearers(permission, skip, limit)
627+
return_if_none!(self.acl_get_storage(), vec![]).get_bearers(permission, skip, limit)
572628
}
573629

574630
fn acl_get_permissioned_accounts(&self) -> #cratename::access_controllable::PermissionedAccounts {
575-
self.#acl_field.get_permissioned_accounts()
631+
return_if_none!(self.acl_get_storage(), get_default_permissioned_accounts()).get_permissioned_accounts()
576632
}
577633
}
578634
};
579635

580636
output.into()
581637
}
582638

583-
fn inject_acl_field(
584-
item: &mut ItemStruct,
585-
field_name: &syn::Ident,
586-
acl_type: &syn::Ident,
587-
) -> Result<(), syn::Error> {
588-
let fields = match item.fields {
589-
syn::Fields::Named(ref mut fields) => fields,
590-
_ => {
591-
return Err(syn::Error::new(
592-
item.ident.span(),
593-
"Expected to have named fields",
594-
))
595-
}
596-
};
597-
598-
fields.named.push(syn::Field::parse_named.parse2(quote! {
599-
#field_name: #acl_type
600-
})?);
601-
Ok(())
602-
}
603-
604639
/// Defines attributes for the `access_control_any` macro.
605640
#[derive(Debug, FromMeta)]
606641
pub struct MacroArgsAny {

near-plugins-derive/tests/contracts/access_controllable/src/lib.rs

+13-16
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,7 @@ impl Counter {
3737
/// identifier of the enum variant, i.e. `"Updater"` for `Role::Updater`.
3838
#[init]
3939
pub fn new(admins: HashMap<String, AccountId>, grantees: HashMap<String, AccountId>) -> Self {
40-
let mut contract = Self {
41-
counter: 0,
42-
// Initialize `AccessControllable` plugin state.
43-
__acl: Default::default(),
44-
};
40+
let mut contract = Self { counter: 0 };
4541

4642
if admins.len() > 0 || grantees.len() > 0 {
4743
// First we make the contract itself super admin to allow it adding admin and grantees.
@@ -68,11 +64,11 @@ impl Counter {
6864
// granting roles, for example:
6965
//
7066
// ```
71-
// contract.__acl.add_admin_unchecked(role, account_id);
72-
// contract.__acl.grant_role_unchecked(role, account_id);
67+
// contract.acl_get_or_init().add_admin_unchecked(role, account_id);
68+
// contract.acl_get_or_init().grant_role_unchecked(role, account_id);
7369
// ```
7470
//
75-
// **Attention**: for security reasons, `__acl.*_unchecked` methods should only be called
71+
// **Attention**: for security reasons, `acl_get_or_init().*_unchecked` methods should only be called
7672
// from within methods with attribute `#[init]` or `#[private]`.
7773
}
7874

@@ -126,7 +122,7 @@ impl Counter {
126122
/// The implementation of `AccessControllable` provided by `near-plugins`
127123
/// adds further methods to the contract which are not part of the trait.
128124
/// Most of them are implemented for the type that holds the plugin's state,
129-
/// here `__acl`.
125+
/// which can be accessed with `self.acl_get_or_init()`.
130126
///
131127
/// This function shows how these methods can be exposed on the contract.
132128
/// Usually this should involve security checks, for example requiring the
@@ -136,7 +132,7 @@ impl Counter {
136132
self.acl_is_super_admin(env::predecessor_account_id()),
137133
"Only super admins are allowed to add other super admins."
138134
);
139-
self.__acl.add_super_admin_unchecked(&account_id)
135+
self.acl_get_or_init().add_super_admin_unchecked(&account_id)
140136
}
141137
}
142138

@@ -145,31 +141,32 @@ impl Counter {
145141
impl Counter {
146142
#[private]
147143
pub fn acl_add_super_admin_unchecked(&mut self, account_id: AccountId) -> bool {
148-
self.__acl.add_super_admin_unchecked(&account_id)
144+
self.acl_get_or_init().add_super_admin_unchecked(&account_id)
149145
}
150146

151147
#[private]
152148
pub fn acl_revoke_super_admin_unchecked(&mut self, account_id: AccountId) -> bool {
153-
self.__acl.revoke_super_admin_unchecked(&account_id)
149+
self.acl_get_or_init().revoke_super_admin_unchecked(&account_id)
154150
}
155151

156152
#[private]
157153
pub fn acl_revoke_role_unchecked(&mut self, role: Role, account_id: AccountId) -> bool {
158-
self.__acl.revoke_role_unchecked(role.into(), &account_id)
154+
self.acl_get_or_init()
155+
.revoke_role_unchecked(role.into(), &account_id)
159156
}
160157

161158
#[private]
162159
pub fn acl_add_admin_unchecked(&mut self, role: Role, account_id: AccountId) -> bool {
163-
self.__acl.add_admin_unchecked(role, &account_id)
160+
self.acl_get_or_init().add_admin_unchecked(role, &account_id)
164161
}
165162

166163
#[private]
167164
pub fn acl_revoke_admin_unchecked(&mut self, role: Role, account_id: AccountId) -> bool {
168-
self.__acl.revoke_admin_unchecked(role, &account_id)
165+
self.acl_get_or_init().revoke_admin_unchecked(role, &account_id)
169166
}
170167

171168
#[private]
172169
pub fn acl_grant_role_unchecked(&mut self, role: Role, account_id: AccountId) -> bool {
173-
self.__acl.grant_role_unchecked(role, &account_id)
170+
self.acl_get_or_init().grant_role_unchecked(role, &account_id)
174171
}
175172
}

near-plugins-derive/tests/contracts/pausable/src/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ impl Counter {
4040
pub fn new(pause_manager: AccountId) -> Self {
4141
let mut contract = Self {
4242
counter: 0,
43-
__acl: Default::default(),
4443
};
4544

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

0 commit comments

Comments
 (0)