diff --git a/contracts/contracts/extensions/Interfaces/IRoleManagement.sol b/contracts/contracts/extensions/Interfaces/IRoleManagement.sol index 504fe5bd6..3010a17c6 100644 --- a/contracts/contracts/extensions/Interfaces/IRoleManagement.sol +++ b/contracts/contracts/extensions/Interfaces/IRoleManagement.sol @@ -10,6 +10,9 @@ interface IRoleManagement { */ error ArraysLengthNotEqual(uint256 lengthArray1, uint256 lengthArray2); + // emitted when trying to remove all admin accounts + error NoAdminsLeft(); + /** * @dev Grant the provided "roles" to all the "accounts", if CASHIN then "amounts" are the allowances * diff --git a/contracts/contracts/extensions/RolesStorageWrapper.sol b/contracts/contracts/extensions/RolesStorageWrapper.sol index c39e5235a..405e669e3 100644 --- a/contracts/contracts/extensions/RolesStorageWrapper.sol +++ b/contracts/contracts/extensions/RolesStorageWrapper.sol @@ -5,6 +5,7 @@ import {IRoles} from './Interfaces/IRoles.sol'; // solhint-disable-next-line max-line-length import {ADMIN_ROLE, _CASHIN_ROLE, _BURN_ROLE, _WIPE_ROLE, _RESCUE_ROLE, _PAUSE_ROLE, _FREEZE_ROLE, _DELETE_ROLE, _WITHOUT_ROLE, _KYC_ROLE, _CUSTOM_FEES_ROLE, _HOLD_CREATOR_ROLE} from '../constants/roles.sol'; import {_ROLES_STORAGE_POSITION} from '../constants/storagePositions.sol'; +import {IRoleManagement} from './Interfaces/IRoleManagement.sol'; abstract contract RolesStorageWrapper { struct MemberData { @@ -123,6 +124,7 @@ abstract contract RolesStorageWrapper { rolesStorage.roles[role].accounts.pop(); delete (rolesStorage.roles[role].members[account]); emit RoleRevoked(role, account, msg.sender); + if (_getNumberOfAccountsWithRole(_getRoleId(IRoles.RoleName.ADMIN)) == 0) revert IRoleManagement.NoAdminsLeft(); } function _getAccountsWithRole(bytes32 role) internal view returns (address[] memory) { diff --git a/contracts/test/thread0/roles.test.ts b/contracts/test/thread0/roles.test.ts index b1019f816..58c49b869 100644 --- a/contracts/test/thread0/roles.test.ts +++ b/contracts/test/thread0/roles.test.ts @@ -136,6 +136,29 @@ describe('➡️ Roles Tests', function () { }) expect(hasBurnRole).to.equals(false) }) + + it('Can not revoke all admin role from a token', async function () { + const Admins = await rolesFacet.getAccountsWithRole(ROLES.defaultAdmin.hash, { + gasLimit: GAS_LIMIT.hederaTokenManager.getAccountsWithRole, + }) + + const Length = Admins.length + + for (let i = 0; i < Length - 1; i++) { + await rolesFacet.revokeRole(ROLES.defaultAdmin.hash, Admins[i], { + gasLimit: GAS_LIMIT.hederaTokenManager.revokeRole, + }) + } + + const revokeRoleResponse = await rolesFacet.revokeRole(ROLES.defaultAdmin.hash, Admins[Length - 1], { + gasLimit: GAS_LIMIT.hederaTokenManager.revokeRole, + }) + await expect( + new ValidateTxResponseCommand({ + txResponse: revokeRoleResponse, + }).execute() + ).to.be.rejectedWith(Error) + }) // * Initial State again it('Getting roles', async function () { diff --git a/contracts/test/thread1/roleManagement.test.ts b/contracts/test/thread1/roleManagement.test.ts index f93c6273a..0f968df81 100644 --- a/contracts/test/thread1/roleManagement.test.ts +++ b/contracts/test/thread1/roleManagement.test.ts @@ -256,6 +256,25 @@ describe('➡️ Role Management Tests', function () { await expect(new ValidateTxResponseCommand({ txResponse }).execute()).to.be.rejectedWith(Error) }) + it('Can not revoke all admin role from a token', async function () { + // Non operator has burn role + const Admins = await rolesFacet.getAccountsWithRole(ROLES.defaultAdmin.hash, { + gasLimit: GAS_LIMIT.hederaTokenManager.getAccountsWithRole, + }) + + const adminAccounts: string[] = [] + + for (let i = 0; i < Admins.length; i++) { + adminAccounts.push(Admins[i]) + } + + const txResponse = await roleManagementFacet.revokeRoles([ROLES.defaultAdmin.hash], adminAccounts, { + gasLimit: GAS_LIMIT.hederaTokenManager.revokeRoles, + }) + + await expect(new ValidateTxResponseCommand({ txResponse }).execute()).to.be.rejectedWith(Error) + }) + it('An account can get all roles of any account', async function () { // Grant roles const Roles = [ROLES.burn.hash]