diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index 7f2fcdc..9283cb9 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -10,7 +10,7 @@ import {IRequestModule} from '../interfaces/modules/request/IRequestModule.sol'; import {IResolutionModule} from '../interfaces/modules/resolution/IResolutionModule.sol'; import {IResponseModule} from '../interfaces/modules/response/IResponseModule.sol'; import {ValidatorLib} from '../libraries/ValidatorLib.sol'; -import {OracleAccessController} from './OracleAccessController.sol'; +import {OracleAccessController} from './access/OracleAccessController.sol'; import {OracleTypehash} from './utils/OracleTypehash.sol'; contract Oracle is IOracle, OracleAccessController { diff --git a/solidity/contracts/CommonAccessController.sol b/solidity/contracts/access/CommonAccessController.sol similarity index 85% rename from solidity/contracts/CommonAccessController.sol rename to solidity/contracts/access/CommonAccessController.sol index 9d795bc..5c8766f 100644 --- a/solidity/contracts/CommonAccessController.sol +++ b/solidity/contracts/access/CommonAccessController.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {IAccessController} from '../interfaces/IAccessController.sol'; -import {IAccessModule} from '../interfaces/modules/access/IAccessModule.sol'; +import {IAccessController} from '../../interfaces/access/IAccessController.sol'; +import {IAccessModule} from '../../interfaces/modules/access/IAccessModule.sol'; abstract contract CommonAccessController is IAccessController { /** diff --git a/solidity/contracts/access/ModuleAccessController.sol b/solidity/contracts/access/ModuleAccessController.sol new file mode 100644 index 0000000..7acc55a --- /dev/null +++ b/solidity/contracts/access/ModuleAccessController.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IOracle} from '../../interfaces/IOracle.sol'; +import {IModuleAccessController} from '../../interfaces/access/IModuleAccessController.sol'; +import {Module} from '../Module.sol'; +import {CommonAccessController} from './CommonAccessController.sol'; + +abstract contract ModuleAccessController is IModuleAccessController, CommonAccessController, Module { + constructor(IOracle _oracle) Module(_oracle) {} + + /** + * @notice Returns an access control object using the contract address as user and the given data + * @dev should only be used by modules as the self-access-control object + * @param _data Arbitrary data + * @return _accessControl The self access control for this contract. + */ + function _defaultAccessControl(bytes memory _data) internal view returns (AccessControl memory _accessControl) { + _accessControl = AccessControl({user: address(this), data: _data}); + } + + /** + * @notice Returns an access control object using the contract address as user, and empty data + * + * @dev should only be used by modules as the self-access-control object + * @return _accessControl The self access control for this contract. + */ + function _defaultAccessControl() internal view returns (AccessControl memory _accessControl) { + _accessControl = _defaultAccessControl(bytes('')); + } + + modifier hasAccess( + address _accessModule, + bytes32 _typehash, + bytes memory _params, + AccessControl memory _accessControl + ) { + if (_accessControl.user != msg.sender) { + if (_accessModule == address(0)) { + revert AccessController_NoAccess(); + } else { + if (!ORACLE.isAccessModuleApproved(_accessControl.user, _accessModule)) { + revert ModuleAccessController_AccessModuleNotApproved(); + } + _hasAccess(_accessModule, _typehash, _params, _accessControl); + } + } + _; + } +} diff --git a/solidity/contracts/OracleAccessController.sol b/solidity/contracts/access/OracleAccessController.sol similarity index 93% rename from solidity/contracts/OracleAccessController.sol rename to solidity/contracts/access/OracleAccessController.sol index faeede9..cdca1b6 100644 --- a/solidity/contracts/OracleAccessController.sol +++ b/solidity/contracts/access/OracleAccessController.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {IOracleAccessController} from '../interfaces/IOracleAccessController.sol'; +import {IOracleAccessController} from '../../interfaces/access/IOracleAccessController.sol'; import {CommonAccessController} from './CommonAccessController.sol'; abstract contract OracleAccessController is IOracleAccessController, CommonAccessController { diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index a0085af..6246de7 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {IAccessController, IOracleAccessController} from './IOracleAccessController.sol'; +import {IAccessController, IOracleAccessController} from './access/IOracleAccessController.sol'; /** * @title Oracle diff --git a/solidity/interfaces/IAccessController.sol b/solidity/interfaces/access/IAccessController.sol similarity index 100% rename from solidity/interfaces/IAccessController.sol rename to solidity/interfaces/access/IAccessController.sol diff --git a/solidity/interfaces/access/IModuleAccessController.sol b/solidity/interfaces/access/IModuleAccessController.sol new file mode 100644 index 0000000..dc6d4ce --- /dev/null +++ b/solidity/interfaces/access/IModuleAccessController.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IAccessController} from './IAccessController.sol'; + +/** + * @title Module Access Controller Interface + * @notice Interface for the module access controller + */ +interface IModuleAccessController is IAccessController { + /** + * @notice Thrown when user has not approved the access module + */ + error ModuleAccessController_AccessModuleNotApproved(); +} diff --git a/solidity/interfaces/IOracleAccessController.sol b/solidity/interfaces/access/IOracleAccessController.sol similarity index 100% rename from solidity/interfaces/IOracleAccessController.sol rename to solidity/interfaces/access/IOracleAccessController.sol diff --git a/solidity/interfaces/modules/access/IAccessModule.sol b/solidity/interfaces/modules/access/IAccessModule.sol index 90f156a..913c36d 100644 --- a/solidity/interfaces/modules/access/IAccessModule.sol +++ b/solidity/interfaces/modules/access/IAccessModule.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {IAccessController} from '../../IAccessController.sol'; import {IModule} from '../../IModule.sol'; +import {IAccessController} from '../../access/IAccessController.sol'; /** * @title AccessModule diff --git a/solidity/test/integration/IntegrationBase.sol b/solidity/test/integration/IntegrationBase.sol index 425278f..701ded5 100644 --- a/solidity/test/integration/IntegrationBase.sol +++ b/solidity/test/integration/IntegrationBase.sol @@ -14,7 +14,7 @@ import {IResolutionModule} from '../../interfaces/modules/resolution/IResolution import {IResponseModule} from '../../interfaces/modules/response/IResponseModule.sol'; import {IOracle, Oracle} from '../../contracts/Oracle.sol'; -import {IAccessController, IOracleAccessController} from '../../interfaces/IOracleAccessController.sol'; +import {IAccessController, IOracleAccessController} from '../../interfaces/access/IOracleAccessController.sol'; import {IMockAccounting, MockAccounting} from '../mocks/contracts/MockAccounting.sol'; import {MockCallback} from '../mocks/contracts/MockCallback.sol'; diff --git a/solidity/test/unit/CommonAccessController.t.sol b/solidity/test/unit/CommonAccessController.t.sol index 12c999d..cb2d43b 100644 --- a/solidity/test/unit/CommonAccessController.t.sol +++ b/solidity/test/unit/CommonAccessController.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import 'forge-std/Test.sol'; -import {CommonAccessController, IAccessController} from '../../contracts/CommonAccessController.sol'; +import {CommonAccessController, IAccessController} from '../../contracts/access/CommonAccessController.sol'; import {IAccessController, IAccessModule} from '../../interfaces/modules/access/IAccessModule.sol'; import {Helpers} from '../utils/Helpers.sol'; diff --git a/solidity/test/unit/ModuleAccessController.t.sol b/solidity/test/unit/ModuleAccessController.t.sol new file mode 100644 index 0000000..9aaa53e --- /dev/null +++ b/solidity/test/unit/ModuleAccessController.t.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IModuleAccessController, ModuleAccessController} from '../../contracts/access/ModuleAccessController.sol'; +import {IOracle} from '../../interfaces/IOracle.sol'; + +import {IOracleAccessController} from '../../interfaces/access/IOracleAccessController.sol'; +import {IAccessController, IAccessModule} from '../../interfaces/modules/access/IAccessModule.sol'; +import {Helpers} from '../utils/Helpers.sol'; +import 'forge-std/Test.sol'; + +contract MockModuleAccessController is ModuleAccessController { + constructor(IOracle _oracle) ModuleAccessController(_oracle) {} + + function hasAccessForTest( + address _accessModule, + bytes32 _typehash, + bytes memory _params, + AccessControl memory _accessControl + ) public hasAccess(_accessModule, _typehash, _params, _accessControl) {} + + function moduleName() external pure returns (string memory _moduleName) { + _moduleName = 'MockModuleAccessController'; + } +} + +/** + * @title Module Abstract Unit tests + */ +contract BaseTest is Test, Helpers { + // A mock access controller + MockModuleAccessController public moduleAccessController; + // The oracle + IOracle public oracle; + // Mock caller + address public caller; + + // Events + event AccessModuleSet(address indexed user, address indexed accessModule, bool approved); + + /** + * @notice Deploy the access controller + */ + function setUp() public { + oracle = IOracle(makeAddr('Oracle')); + moduleAccessController = new MockModuleAccessController(oracle); + caller = makeAddr('Caller'); + } + + function _setAccessModuleForTest(address _user, address _accessModule, bool _approved) public { + vm.mockCall( + address(oracle), + abi.encodeWithSelector(IOracleAccessController.isAccessModuleApproved.selector, _user, _accessModule), + abi.encode(_approved) + ); + } +} + +contract ModuleAccessController_Unit_HasAccess is BaseTest { + modifier happyPath( + address _accessModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) { + vm.assume(_accessControl.user != caller); + vm.assume(_accessModule != address(0) && _accessModule != caller); + vm.assume(_params.length < 32); + + _setAccessModuleForTest(_accessControl.user, _accessModule, true); + + vm.startPrank(caller); + _; + } + + function test_revertIfNoAccessModuleAndSenderIsNotUser( + address _accessModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) public happyPath(_accessModule, _typehash, _params, _accessControl) { + // Expect the revert + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); + + // Call the hasAccess function + moduleAccessController.hasAccessForTest(address(0), _typehash, _params, _accessControl); + } + + function test_revertIfAccessModuleNotApproved( + address _accessModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) public happyPath(_accessModule, _typehash, _params, _accessControl) { + // Ensure the access module is not approved + _setAccessModuleForTest(_accessControl.user, _accessModule, false); + + // Expect the revert + vm.expectRevert( + abi.encodeWithSelector(IModuleAccessController.ModuleAccessController_AccessModuleNotApproved.selector) + ); + + // Call the hasAccess function + moduleAccessController.hasAccessForTest(_accessModule, _typehash, _params, _accessControl); + } + + function test_revertIfNoHasAccess( + address _accessModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) public happyPath(_accessModule, _typehash, _params, _accessControl) { + IAccessModule.AccessControlParameters memory _expectedParams = IAccessModule.AccessControlParameters({ + sender: caller, + accessControl: _accessControl, + typehash: _typehash, + typehashParams: _params + }); + + // Expect the hasAccess function to not be called + _mockAndExpect( + _accessModule, + abi.encodeWithSelector(IAccessModule.hasAccess.selector, abi.encode(_expectedParams)), + abi.encode(false) + ); + + // Expect the revert + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); + + // Call the hasAccess function + moduleAccessController.hasAccessForTest(_accessModule, _typehash, _params, _accessControl); + } + + function test_hasAccessSenderIsNotUser( + address _accessModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) public happyPath(_accessModule, _typehash, _params, _accessControl) { + IAccessModule.AccessControlParameters memory _expectedParams = IAccessModule.AccessControlParameters({ + sender: caller, + accessControl: _accessControl, + typehash: _typehash, + typehashParams: _params + }); + + // Expect the hasAccess function to be called + _mockAndExpect( + _accessModule, + abi.encodeWithSelector(IAccessModule.hasAccess.selector, abi.encode(_expectedParams)), + abi.encode(true) + ); + + // Call the hasAccess function + moduleAccessController.hasAccessForTest(_accessModule, _typehash, _params, _accessControl); + } + + function test_hasAccessSenderIsUser( + address _accessModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) public happyPath(_accessModule, _typehash, _params, _accessControl) { + _setAccessModuleForTest(caller, _accessModule, true); + _accessControl.user = caller; + // We expect the hasAccess function to not be called + vm.expectCall( + _accessModule, + abi.encodeWithSelector( + IAccessModule.hasAccess.selector, + abi.encode( + IAccessModule.AccessControlParameters({ + sender: caller, + accessControl: _accessControl, + typehash: _typehash, + typehashParams: _params + }) + ) + ), + 0 + ); + moduleAccessController.hasAccessForTest(_accessModule, _typehash, _params, _accessControl); + } +} diff --git a/solidity/test/unit/Oracle.t.sol b/solidity/test/unit/Oracle.t.sol index 6b7084b..6034398 100644 --- a/solidity/test/unit/Oracle.t.sol +++ b/solidity/test/unit/Oracle.t.sol @@ -8,7 +8,7 @@ import {IOracle} from '../../interfaces/IOracle.sol'; import {IDisputeModule} from '../../interfaces/modules/dispute/IDisputeModule.sol'; -import {IAccessController, IOracleAccessController} from '../../interfaces/IOracleAccessController.sol'; +import {IAccessController, IOracleAccessController} from '../../interfaces/access/IOracleAccessController.sol'; import {IAccessModule} from '../../interfaces/modules/access/IAccessModule.sol'; import {IFinalityModule} from '../../interfaces/modules/finality/IFinalityModule.sol'; import {IRequestModule} from '../../interfaces/modules/request/IRequestModule.sol'; diff --git a/solidity/test/unit/OracleAccessController.t.sol b/solidity/test/unit/OracleAccessController.t.sol index 9e3bb56..528e82f 100644 --- a/solidity/test/unit/OracleAccessController.t.sol +++ b/solidity/test/unit/OracleAccessController.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import 'forge-std/Test.sol'; -import {IOracleAccessController, OracleAccessController} from '../../contracts/OracleAccessController.sol'; +import {IOracleAccessController, OracleAccessController} from '../../contracts/access/OracleAccessController.sol'; import {IAccessController, IAccessModule} from '../../interfaces/modules/access/IAccessModule.sol'; import {Helpers} from '../utils/Helpers.sol'; diff --git a/solidity/test/utils/Helpers.sol b/solidity/test/utils/Helpers.sol index 0e79edb..c282ab4 100644 --- a/solidity/test/utils/Helpers.sol +++ b/solidity/test/utils/Helpers.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {IOracle} from '../../contracts/Oracle.sol'; -import {IAccessController} from '../../interfaces/IAccessController.sol'; +import {IAccessController} from '../../interfaces/access/IAccessController.sol'; import {Test} from 'forge-std/Test.sol'; contract Helpers is Test {