Skip to content

Commit

Permalink
feat(Acl): add acl_transfer_super_admin (Near-One#94)
Browse files Browse the repository at this point in the history
* ACL: Add transfer super admin api

* Apply suggestions from code review

Co-authored-by: mooori <[email protected]>

---------

Co-authored-by: mooori <[email protected]>
  • Loading branch information
karim-en and mooori authored Mar 15, 2023
1 parent 8920f9a commit 18b08df
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 0 deletions.
14 changes: 14 additions & 0 deletions near-plugins-derive/src/access_controllable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,16 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
Some(self.revoke_super_admin_unchecked(account_id))
}

fn transfer_super_admin(&mut self, account_id: &::near_sdk::AccountId) -> Option<bool> {
let current_super_admin = ::near_sdk::env::predecessor_account_id();
if !self.is_super_admin(&current_super_admin) {
return None;
}

self.revoke_super_admin_unchecked(&current_super_admin);
Some(self.add_super_admin_unchecked(&account_id))
}

/// Revokes super-admin permissions from `account_id` without checking any
/// permissions. It returns whether `account_id` was a super-admin.
fn revoke_super_admin_unchecked(&mut self, account_id: &::near_sdk::AccountId) -> bool {
Expand Down Expand Up @@ -565,6 +575,10 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
self.acl_get_or_init().revoke_super_admin(&account_id)
}

fn acl_transfer_super_admin(&mut self, account_id: ::near_sdk::AccountId) -> Option<bool> {
self.acl_get_or_init().transfer_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_get_or_init().add_admin(role, &account_id)
Expand Down
55 changes: 55 additions & 0 deletions near-plugins-derive/tests/access_controllable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,61 @@ async fn test_acl_revoke_super_admin() -> anyhow::Result<()> {
Ok(())
}

#[tokio::test]
async fn test_acl_transfer_super_admin() -> anyhow::Result<()> {
let setup = Setup::new().await?;
let super_admin = setup.new_super_admin_account().await?;
let new_super_admin = setup.worker.dev_create_account().await?;

setup
.contract
.assert_acl_is_super_admin(true, setup.contract_account(), super_admin.id())
.await;

// Create caller account.
let caller_unauth = setup.worker.dev_create_account().await?;

// Transfer is a no-op if caller is not a super-admin.
let res = setup
.contract
.acl_transfer_super_admin(&caller_unauth, super_admin.id())
.await?;
assert_eq!(res, None);
setup
.contract
.assert_acl_is_super_admin(true, setup.contract_account(), super_admin.id())
.await;
setup
.contract
.assert_acl_is_super_admin(false, setup.contract_account(), new_super_admin.id())
.await;

// Transfer succeeds if the caller is a super-admin.
let res = setup
.contract
.acl_transfer_super_admin(&super_admin, new_super_admin.id())
.await?;
assert_eq!(res, Some(true));
setup
.contract
.assert_acl_is_super_admin(false, setup.contract_account(), super_admin.id())
.await;
setup
.contract
.assert_acl_is_super_admin(true, setup.contract_account(), new_super_admin.id())
.await;

// Transfer to an account that is already super-admin returns `Some(false)`.
let admin = setup.new_super_admin_account().await?;
let res = setup
.contract
.acl_transfer_super_admin(&new_super_admin, admin.id())
.await?;
assert_eq!(res, Some(false));

Ok(())
}

#[tokio::test]
async fn test_acl_revoke_super_admin_unchecked() -> anyhow::Result<()> {
let setup = Setup::new().await?;
Expand Down
18 changes: 18 additions & 0 deletions near-plugins-derive/tests/common/access_controllable_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ impl AccessControllableContract {
Ok(res)
}

pub async fn acl_transfer_super_admin(
&self,
caller: &Account,
account_id: &AccountId,
) -> anyhow::Result<Option<bool>> {
let res = caller
.call(self.contract.id(), "acl_transfer_super_admin")
.args_json(json!({
"account_id": account_id,
}))
.max_gas()
.transact()
.await?
.into_result()?
.json::<Option<bool>>()?;
Ok(res)
}

pub async fn acl_revoke_super_admin_unchecked(
&self,
caller: &Account,
Expand Down
39 changes: 39 additions & 0 deletions near-plugins/src/access_controllable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,45 @@ pub trait AccessControllable {
/// ```
fn acl_revoke_super_admin(&mut self, account_id: AccountId) -> Option<bool>;

/// Transfer super-admin permissions from the predecessor to `account_id` provided that the
/// predecessor has sufficient permissions, i.e. is a super-admin as defined
/// by [`acl_is_super_admin`]. This function allows a super-admin to revoke the permission from
/// themselves and add `account_id` as super-admin. While it is a helper for use cases which
/// require this transfer, it should be noted that `AccessControllable` allows having more than
/// one super-admin.
///
/// In case of sufficient permissions, the returned `Some(bool)` indicates
/// whether `account_id` is a new super-admin. Without permissions, `None` is
/// returned and internal state is not modified.
///
/// If super-admin permissions are transferred, the following events will be
/// emitted:
///
/// ```json
/// {
/// "standard":"AccessControllable",
/// "version":"1.0.0",
/// "event":"super_admin_revoked",
/// "data":{
/// "account":"<PREVIOUSLY_SUPER_ADMIN>",
/// "by":"<SUPER_ADMIN>"
/// }
/// }
/// ```
///
/// ```json
/// {
/// "standard":"AccessControllable",
/// "version":"1.0.0",
/// "event":"super_admin_added",
/// "data":{
/// "account":"<SUPER_ADMIN_ACCOUNT>",
/// "by":"<CONTRACT_ACCOUNT>"
/// }
/// }
/// ```
fn acl_transfer_super_admin(&mut self, account_id: AccountId) -> Option<bool>;

/// Makes `account_id` an admin provided that the predecessor has sufficient
/// permissions, i.e. is an admin as defined by [`acl_is_admin`].
///
Expand Down

0 comments on commit 18b08df

Please sign in to comment.