Skip to content

Commit

Permalink
Merge pull request #254 from CMTA/authorizationEngine
Browse files Browse the repository at this point in the history
Add authorizationEngine
  • Loading branch information
rya-sge authored Dec 6, 2023
2 parents f007e1f + df2dec0 commit d41d4c5
Show file tree
Hide file tree
Showing 20 changed files with 128 additions and 162 deletions.
5 changes: 3 additions & 2 deletions contracts/CMTAT_STANDALONE.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ contract CMTAT_STANDALONE is CMTAT_BASE {
* @notice Contract version for standalone deployment
* @param forwarderIrrevocable address of the forwarder, required for the gasless support
* @param admin address of the admin of contract (Access Control)
* @param authorizationEngineIrrevocable
* @param nameIrrevocable name of the token
* @param symbolIrrevocable name of the symbol
* @param decimalsIrrevocable number of decimals used to get its user representation, should be 0 to be compliant with the CMTAT specifications.
Expand All @@ -22,7 +23,7 @@ contract CMTAT_STANDALONE is CMTAT_BASE {
constructor(
address forwarderIrrevocable,
address admin,
uint48 initialDelay,
IAuthorizationEngine authorizationEngineIrrevocable,
string memory nameIrrevocable,
string memory symbolIrrevocable,
uint8 decimalsIrrevocable,
Expand All @@ -36,7 +37,7 @@ contract CMTAT_STANDALONE is CMTAT_BASE {
// Warning : do not initialize the proxy
initialize(
admin,
initialDelay,
authorizationEngineIrrevocable,
nameIrrevocable,
symbolIrrevocable,
decimalsIrrevocable,
Expand Down
13 changes: 13 additions & 0 deletions contracts/interfaces/IAuthorizationEngine.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MPL-2.0

pragma solidity ^0.8.0;

interface IAuthorizationEngine {
/**
* @dev Returns true if the operation is a success, and false otherwise.
*/
function operateOnAuthorization(
bytes32 role, address account
) external returns (bool isValid);

}
2 changes: 2 additions & 0 deletions contracts/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ library Errors {

// AuthorizationModule
error CMTAT_AuthorizationModule_AddressZeroNotAllowed();
error CMTAT_AuthorizationModule_InvalidAuthorization();
error CMTAT_AuthorizationModule_AuthorizationEngineAlreadySet();

// PauseModule
error CMTAT_PauseModule_ContractIsDeactivated();
Expand Down
9 changes: 4 additions & 5 deletions contracts/modules/CMTAT_BASE.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ abstract contract CMTAT_BASE is
*/
function initialize(
address admin,
uint48 initialDelayToAcceptAdminRole,
IAuthorizationEngine authorizationEngineIrrevocable,
string memory nameIrrevocable,
string memory symbolIrrevocable,
uint8 decimalsIrrevocable,
Expand All @@ -68,7 +68,7 @@ abstract contract CMTAT_BASE is
) public initializer {
__CMTAT_init(
admin,
initialDelayToAcceptAdminRole,
authorizationEngineIrrevocable,
nameIrrevocable,
symbolIrrevocable,
decimalsIrrevocable,
Expand All @@ -85,7 +85,7 @@ abstract contract CMTAT_BASE is
*/
function __CMTAT_init(
address admin,
uint48 initialDelayToAcceptAdminRole,
IAuthorizationEngine authorizationEngineIrrevocable,
string memory nameIrrevocable,
string memory symbolIrrevocable,
uint8 decimalsIrrevocable,
Expand All @@ -103,7 +103,6 @@ abstract contract CMTAT_BASE is
__ERC165_init_unchained();
// AuthorizationModule inherits from AccessControlUpgradeable
__AccessControl_init_unchained();
__AccessControlDefaultAdminRules_init_unchained(initialDelayToAcceptAdminRole, admin);
__Pausable_init_unchained();

/* Internal Modules */
Expand All @@ -119,7 +118,7 @@ abstract contract CMTAT_BASE is

/* Wrapper */
// AuthorizationModule_init_unchained is called firstly due to inheritance
__AuthorizationModule_init_unchained();
__AuthorizationModule_init_unchained(admin, authorizationEngineIrrevocable);
__ERC20BurnModule_init_unchained();
__ERC20MintModule_init_unchained();
// EnforcementModule_init_unchained is called before ValidationModule_init_unchained due to inheritance
Expand Down
78 changes: 61 additions & 17 deletions contracts/modules/security/AuthorizationModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

pragma solidity ^0.8.20;

import "../../../openzeppelin-contracts-upgradeable/contracts/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol";
import "../../../openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol";
import "../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";

import "../../libraries/Errors.sol";

abstract contract AuthorizationModule is AccessControlDefaultAdminRulesUpgradeable {
import "../../interfaces/IAuthorizationEngine.sol";
abstract contract AuthorizationModule is AccessControlUpgradeable {
IAuthorizationEngine private authorizationEngine;
/**
* @dev Emitted when a rule engine is set.
*/
event AuthorizationEngine(IAuthorizationEngine indexed newAuthorizationEngine);
// BurnModule
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
// CreditEvents
Expand All @@ -31,34 +36,73 @@ abstract contract AuthorizationModule is AccessControlDefaultAdminRulesUpgradeab
* - The control of the zero address is done by AccessControlDefaultAdminRules
*
*/
function __AuthorizationModule_init_unchained(
) internal view onlyInitializing {
function __AuthorizationModule_init_unchained(address admin, IAuthorizationEngine authorizationEngine_)
internal onlyInitializing {
if(admin == address(0)){
revert Errors.CMTAT_AuthorizationModule_AddressZeroNotAllowed();
}
_grantRole(DEFAULT_ADMIN_ROLE, admin);
if (address(authorizationEngine) != address (0)) {
authorizationEngine = authorizationEngine_;
}


}


/*
@notice set an authorizationEngine if not already set
@dev once an AuthorizationEngine is set, it is not possible to unset it
*/
function setAuthorizationEngine(
IAuthorizationEngine authorizationEngine_
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (address(authorizationEngine) != address (0)){
revert Errors.CMTAT_AuthorizationModule_AuthorizationEngineAlreadySet();
}
authorizationEngine = authorizationEngine_;
emit AuthorizationEngine(authorizationEngine_);
}

function grantRole(bytes32 role, address account) public override onlyRole(getRoleAdmin(role)) {
if (address(authorizationEngine) != address (0)) {
bool result = authorizationEngine.operateOnAuthorization(role, account);
if(!result) {
// Operation rejected by the authorizationEngine
revert Errors.CMTAT_AuthorizationModule_InvalidAuthorization();
}
}
return AccessControlUpgradeable.grantRole(role, account);
}




function revokeRole(bytes32 role, address account) public override onlyRole(getRoleAdmin(role)) {
if (address(authorizationEngine) != address (0)) {
bool result = authorizationEngine.operateOnAuthorization(role, account);
if(!result) {
// Operation rejected by the authorizationEngine
revert Errors.CMTAT_AuthorizationModule_InvalidAuthorization();
}
}
return AccessControlUpgradeable.revokeRole(role, account);
}


/*
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(
bytes32 role,
address account
) public view virtual override( IAccessControl, AccessControlUpgradeable) returns (bool) {
) public view virtual override(AccessControlUpgradeable) returns (bool) {
// The Default Admin has all roles
if (AccessControlUpgradeable.hasRole(DEFAULT_ADMIN_ROLE, account)) {
return true;
}
return AccessControlUpgradeable.hasRole(role, account);
}

/**
@notice
Warning: this function should be called only in case of necessity (e.g private key leak)
Its goal is to transfer the adminship of the contract to a new admin, whithout delay.
The prefer way is to use the workflow of AccessControlDefaultAdminRulesUpgradeable
*/
function transferAdminshipDirectly(address newAdmin) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
// we revoke first the admin since we can only have one admin
_revokeRole(DEFAULT_ADMIN_ROLE, _msgSender());
_grantRole(DEFAULT_ADMIN_ROLE, newAdmin);
}
uint256[50] private __gap;
}
2 changes: 1 addition & 1 deletion contracts/modules/wrapper/core/BaseModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ abstract contract BaseModule is AuthorizationModule {
@notice
Get the current version of the smart contract
*/
string public constant VERSION = "2.3.1";
string public constant VERSION = "2.4.0";
/* Events */
event Term(string indexed newTermIndexed, string newTerm);
event TokenId(string indexed newTokenIdIndexed, string newTokenId);
Expand Down
4 changes: 2 additions & 2 deletions contracts/test/CMTATSnapshot/CMTATSnapshotStandaloneTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ contract CMTATSnapshotStandaloneTest is CMTAT_BASE_SnapshotTest {
constructor(
address forwarderIrrevocable,
address admin,
uint48 initialDelayToAcceptAdminRole,
IAuthorizationEngine authorizationEngineIrrevocable,
string memory nameIrrevocable,
string memory symbolIrrevocable,
uint8 decimalsIrrevocable,
Expand All @@ -35,7 +35,7 @@ contract CMTATSnapshotStandaloneTest is CMTAT_BASE_SnapshotTest {
// Warning : do not initialize the proxy
initialize(
admin,
initialDelayToAcceptAdminRole,
authorizationEngineIrrevocable,
nameIrrevocable,
symbolIrrevocable,
decimalsIrrevocable,
Expand Down
9 changes: 4 additions & 5 deletions contracts/test/CMTATSnapshot/CMTAT_BASE_SnapshotTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ abstract contract CMTAT_BASE_SnapshotTest is
*/
function initialize(
address admin,
uint48 initialDelayToAcceptAdminRole,
IAuthorizationEngine authorizationEngineIrrevocable,
string memory nameIrrevocable,
string memory symbolIrrevocable,
uint8 decimalsIrrevocable,
Expand All @@ -59,7 +59,7 @@ abstract contract CMTAT_BASE_SnapshotTest is
) public initializer {
__CMTAT_init(
admin,
initialDelayToAcceptAdminRole,
authorizationEngineIrrevocable,
nameIrrevocable,
symbolIrrevocable,
decimalsIrrevocable,
Expand All @@ -76,7 +76,7 @@ abstract contract CMTAT_BASE_SnapshotTest is
*/
function __CMTAT_init(
address admin,
uint48 initialDelayToAcceptAdminRole,
IAuthorizationEngine authorizationEngineIrrevocable,
string memory nameIrrevocable,
string memory symbolIrrevocable,
uint8 decimalsIrrevocable,
Expand Down Expand Up @@ -109,8 +109,7 @@ abstract contract CMTAT_BASE_SnapshotTest is

/* Wrapper */
// AuthorizationModule_init_unchained is called firstly due to inheritance
__AuthorizationModule_init_unchained();
__AccessControlDefaultAdminRules_init_unchained(initialDelayToAcceptAdminRole, admin);
__AuthorizationModule_init_unchained(admin, authorizationEngineIrrevocable);
__ERC20BurnModule_init_unchained();
__ERC20MintModule_init_unchained();
// EnforcementModule_init_unchained is called before ValidationModule_init_unchained due to inheritance
Expand Down
94 changes: 10 additions & 84 deletions test/common/AuthorizationModule/AuthorizationModuleCommon.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
const { expectEvent, time } = require('@openzeppelin/test-helpers')
const { expectEvent } = require('@openzeppelin/test-helpers')
const {
expectRevertCustomError
} = require('../../../openzeppelin-contracts-upgradeable/test/helpers/customError')
const { PAUSER_ROLE, DEFAULT_ADMIN_ROLE } = require('../../utils')
const { PAUSER_ROLE, DEFAULT_ADMIN_ROLE, ZERO_ADDRESS } = require('../../utils')
const chai = require('chai')
const should = chai.should()
const { DEFAULT_ADMIN_DELAY_WEB3 } = require('../../deploymentUtils')
function AuthorizationModuleCommon (admin, address1, address2) {
context('Authorization', function () {
it('testAdminCanGrantRole', async function () {
Expand Down Expand Up @@ -76,88 +75,15 @@ function AuthorizationModuleCommon (admin, address1, address2) {
// Assert
(await this.cmtat.hasRole(PAUSER_ROLE, address1)).should.equal(true)
})
})

it('testCanAdminTransferAdminship', async function () {
// Arrange - Assert
(await this.cmtat.owner()).should.equal(admin)
// Act 1
// Starts an admin transfer
await this.cmtat.beginDefaultAdminTransfer(address1, { from: admin })

// Wait for acceptance
const acceptSchedule = web3.utils
.toBN(await time.latest())
.add(DEFAULT_ADMIN_DELAY_WEB3)
// We jump into the future
await time.increase(acceptSchedule.addn(1))

// Act 2
this.logs = await this.cmtat.acceptDefaultAdminTransfer({ from: address1 });

// Assert
(await this.cmtat.owner()).should.equal(address1)
// Events
expectEvent(this.logs, 'RoleRevoked', {
role: DEFAULT_ADMIN_ROLE,
account: admin
})
expectEvent(this.logs, 'RoleGranted', {
role: DEFAULT_ADMIN_ROLE,
account: address1
})
})

/*
Already tested by OpenZeppelin library
*/
it('testCannotNonAdminTransferAdminship', async function () {
// Arrange - Assert
(await this.cmtat.owner()).should.equal(admin)
// Act
// Starts an admin transfer
await expectRevertCustomError(
this.cmtat.beginDefaultAdminTransfer(address1, { from: address1 }),
'AccessControlUnauthorizedAccount',
[address1, DEFAULT_ADMIN_ROLE]
);
// Assert
(await this.cmtat.owner()).should.equal(admin)
})

it('testCanAdminTransferAdminshipDirectly', async function () {
// Arrange - Assert
(await this.cmtat.owner()).should.equal(admin)

// Act
// Transfer the rights
await this.cmtat.transferAdminshipDirectly(address1, { from: admin });

// Assert
(await this.cmtat.owner()).should.equal(address1)
// Events
expectEvent(this.logs, 'RoleRevoked', {
role: DEFAULT_ADMIN_ROLE,
account: admin
})
expectEvent(this.logs, 'RoleGranted', {
role: DEFAULT_ADMIN_ROLE,
account: address1
})
})

it('testCannotNonAdminTransferAdminshipDirectly', async function () {
// Arrange - Assert
(await this.cmtat.owner()).should.equal(admin)
// Transfer the rights
await expectRevertCustomError(
this.cmtat.transferAdminshipDirectly(address1, { from: address2 }),
'AccessControlUnauthorizedAccount',
[address2, DEFAULT_ADMIN_ROLE]
);

// Assert
(await this.cmtat.owner()).should.equal(admin)
})
it('testCannotNonAdminSetAuthorizationEngine', async function () {
// Act
await expectRevertCustomError(
this.cmtat.setAuthorizationEngine(ZERO_ADDRESS, { from: address1 }),
'AccessControlUnauthorizedAccount',
[address1, DEFAULT_ADMIN_ROLE]
)
})
}
module.exports = AuthorizationModuleCommon
2 changes: 1 addition & 1 deletion test/common/BaseModuleCommon.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function BaseModuleCommon (owner, address1, address2, address3, proxyTest) {
context('Token structure', function () {
it('testHasTheDefinedVersion', async function () {
// Act + Assert
(await this.cmtat.VERSION()).should.equal('2.3.1')
(await this.cmtat.VERSION()).should.equal('2.4.0')
})
it('testHasTheDefinedTokenId', async function () {
// Act + Assert
Expand Down
Loading

0 comments on commit d41d4c5

Please sign in to comment.