From 69a8be98209d3f26b37cb04e2900919d5ef8c0bf Mon Sep 17 00:00:00 2001 From: zorzal Date: Wed, 27 Nov 2024 07:43:54 -0500 Subject: [PATCH 1/6] feat: add WhitelistAccessModule, along with its interface --- .../modules/access/WhitelistAccessModule.sol | 44 +++++++++++++++++ .../modules/access/IWhitelistAccessModule.sol | 48 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 solidity/contracts/examples/modules/access/WhitelistAccessModule.sol create mode 100644 solidity/interfaces/examples/modules/access/IWhitelistAccessModule.sol diff --git a/solidity/contracts/examples/modules/access/WhitelistAccessModule.sol b/solidity/contracts/examples/modules/access/WhitelistAccessModule.sol new file mode 100644 index 0000000..edb8564 --- /dev/null +++ b/solidity/contracts/examples/modules/access/WhitelistAccessModule.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IModule, Module} from '@defi-wonderland/prophet-core/solidity/contracts/Module.sol'; +import {IOracle} from '@defi-wonderland/prophet-core/solidity/interfaces/IOracle.sol'; + +import { + IAccessModule, + IWhitelistAccessModule +} from '../../../../interfaces/examples/modules/access/IWhitelistAccessModule.sol'; + +contract WhitelistAccessModule is Module, IWhitelistAccessModule { + /// @inheritdoc IWhitelistAccessModule + mapping(address _user => mapping(address _allowedSender => bool _status)) public whitelist; + + constructor(IOracle _oracle) Module(_oracle) {} + + /// @inheritdoc IModule + function moduleName() external pure returns (string memory _moduleName) { + return 'WhitelistAccessModule'; + } + + /// @inheritdoc IAccessModule + function decodeAccessControlParameters(bytes calldata _data) + public + pure + returns (IAccessModule.AccessControlParameters memory _params) + { + _params = abi.decode(_data, (IAccessModule.AccessControlParameters)); + } + + /// @inheritdoc IAccessModule + function hasAccess(bytes calldata _data) external view returns (bool _hasAccess) { + IAccessModule.AccessControlParameters memory _params = decodeAccessControlParameters(_data); + + _hasAccess = whitelist[_params.accessControl.user][_params.sender]; + } + + /// @inheritdoc IWhitelistAccessModule + function setSenderApproval(address _allowedSender, bool _status) external { + whitelist[msg.sender][_allowedSender] = _status; + emit SetApproval(msg.sender, _allowedSender, _status); + } +} diff --git a/solidity/interfaces/examples/modules/access/IWhitelistAccessModule.sol b/solidity/interfaces/examples/modules/access/IWhitelistAccessModule.sol new file mode 100644 index 0000000..87d2553 --- /dev/null +++ b/solidity/interfaces/examples/modules/access/IWhitelistAccessModule.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IAccessModule} from '@defi-wonderland/prophet-core/solidity/interfaces/modules/access/IAccessModule.sol'; + +/* + * @title IWhitelistAccessModule + */ +interface IWhitelistAccessModule is IAccessModule { + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ + + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Emitted when the approval status of a user is updated. + * @param _user The address of the user authorizing a sender. + * @param _sender The address of the sender whose status is being updated + * @param _status True if approved, false otherwise. + */ + event SetApproval(address _user, address _sender, bool _status); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Checks if a user has whitelisted the sender address. + * @param _user The address of the user whose whitelist is being checked. + * @param _sender The address of the sender to check against the user's whitelist. + * @return _status True if approved, false otherwise. + */ + function whitelist(address _user, address _sender) external view returns (bool _status); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Sets the approval status for a given sender address. + * @param _allowedSender The address of the sender to be approved or disapproved. + * @param _status True if approved, false otherwise. + */ + function setSenderApproval(address _allowedSender, bool _status) external; +} From 728e0e8d6ad624129ce5fff292e6d653c73cd95d Mon Sep 17 00:00:00 2001 From: zorzal Date: Wed, 27 Nov 2024 07:47:18 -0500 Subject: [PATCH 2/6] test: add WhitelistAccessModule unit tests --- .../access/WhitelistAccessModule.t.sol | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 solidity/test/unit/modules/access/WhitelistAccessModule.t.sol diff --git a/solidity/test/unit/modules/access/WhitelistAccessModule.t.sol b/solidity/test/unit/modules/access/WhitelistAccessModule.t.sol new file mode 100644 index 0000000..db1e748 --- /dev/null +++ b/solidity/test/unit/modules/access/WhitelistAccessModule.t.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import 'forge-std/Test.sol'; + +import {Helpers} from '../../../utils/Helpers.sol'; + +import {IModule} from '@defi-wonderland/prophet-core/solidity/interfaces/IModule.sol'; +import {IOracle} from '@defi-wonderland/prophet-core/solidity/interfaces/IOracle.sol'; +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import { + IAccessModule, + IWhitelistAccessModule, + WhitelistAccessModule +} from '../../../../contracts/examples/modules/access/WhitelistAccessModule.sol'; + +contract ForTest_WhitelistAccessModule is WhitelistAccessModule { + constructor(IOracle _oracle) WhitelistAccessModule(_oracle) {} + + function forTest_setSenderApproval(address _user, address _allowedSender, bool _status) public { + whitelist[_user][_allowedSender] = _status; + } +} + +/** + * @title Whitelist Access Module unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + ForTest_WhitelistAccessModule public whitelistAccessModule; + // A mock oracle + IOracle public oracle; + + event SetApproval(address _user, address _sender, bool _status); + + function setUp() public { + oracle = IOracle(makeAddr('Oracle')); + vm.etch(address(oracle), hex'069420'); + // + whitelistAccessModule = new ForTest_WhitelistAccessModule(oracle); + } +} + +contract WhitelistAccessModule_Unit_ModuleData is BaseTest { + function test_decodeAccessControlParameters_decodesSuccessfully(IAccessModule.AccessControlParameters memory _params) + public + view + { + // Test: decode the given access control parameters + IAccessModule.AccessControlParameters memory _decodedParams = + whitelistAccessModule.decodeAccessControlParameters(abi.encode(_params)); + + // Check: decoded values match original values? + assertEq(_decodedParams.sender, _params.sender); + assertEq(_decodedParams.typehash, _params.typehash); + assertEq(_decodedParams.typehashParams, _params.typehashParams); + assertEq(_decodedParams.accessControl.user, _params.accessControl.user); + assertEq(_decodedParams.accessControl.data, _params.accessControl.data); + } + + function test_moduleName_ReturnsCorrectName() public view { + assertEq(whitelistAccessModule.moduleName(), 'WhitelistAccessModule'); + } +} + +contract WhitelistAccessModule_Unit_HasAccess is BaseTest { + function test_hasAccess_authorizedSender(IAccessModule.AccessControlParameters memory _params) public { + // set _params.sender` as an allowed sender for `_params.accessControl.user` + whitelistAccessModule.forTest_setSenderApproval(_params.accessControl.user, _params.sender, true); + + assertTrue(whitelistAccessModule.hasAccess(abi.encode(_params))); + } + + function test_hasAccess_unauthorizedSender(IAccessModule.AccessControlParameters memory _params) public { + // set _params.sender` as an unallowed sender for `_params.accessControl.user` + whitelistAccessModule.forTest_setSenderApproval(_params.accessControl.user, _params.sender, false); + + assertFalse(whitelistAccessModule.hasAccess(abi.encode(_params))); + } + + /// @notice By default, authorization is denied + function test_hasAccess_unauthorizedByDefault(IAccessModule.AccessControlParameters memory _params) public { + assertFalse(whitelistAccessModule.hasAccess(abi.encode(_params))); + } +} + +contract WhitelistAccessModule_Unit_setSenderApproval is BaseTest { + function test_setSenderApproval_updatesState(address _user, address _sender, bool _status) public { + vm.prank(_user); + whitelistAccessModule.setSenderApproval(_sender, _status); + + assertEq(whitelistAccessModule.whitelist(_user, _sender), _status); + } + + function test_setSenderApproval_emitsEvent(address _user, address _sender, bool _status) public { + // Expect the event with correct parameters + vm.expectEmit(address(whitelistAccessModule)); + emit SetApproval(_user, _sender, _status); + + // Test: call setSenderApproval + vm.prank(_user); + whitelistAccessModule.setSenderApproval(_sender, _status); + } + + function test_setSenderApproval_CanUpdateStatus(address _user, address _sender, bool _status) public { + // Expect the event with correct parameters + vm.expectEmit(address(whitelistAccessModule)); + emit SetApproval(_user, _sender, _status); + + // sets `_status` for `_sender` + vm.prank(_user); + whitelistAccessModule.setSenderApproval(_sender, _status); + + vm.expectEmit(address(whitelistAccessModule)); + emit SetApproval(_user, _sender, !_status); + + // sets `!_status` for `_sender` + vm.prank(_user); + whitelistAccessModule.setSenderApproval(_sender, !_status); + } +} From 6550a32ef84bb2cd5a65dd16f79fc58b148116db Mon Sep 17 00:00:00 2001 From: zorzal Date: Wed, 27 Nov 2024 08:08:22 -0500 Subject: [PATCH 3/6] chore: lint --- .../modules/access/WhitelistAccessModule.t.sol | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/solidity/test/unit/modules/access/WhitelistAccessModule.t.sol b/solidity/test/unit/modules/access/WhitelistAccessModule.t.sol index db1e748..3e455fc 100644 --- a/solidity/test/unit/modules/access/WhitelistAccessModule.t.sol +++ b/solidity/test/unit/modules/access/WhitelistAccessModule.t.sol @@ -1,19 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import 'forge-std/Test.sol'; - -import {Helpers} from '../../../utils/Helpers.sol'; - -import {IModule} from '@defi-wonderland/prophet-core/solidity/interfaces/IModule.sol'; import {IOracle} from '@defi-wonderland/prophet-core/solidity/interfaces/IOracle.sol'; -import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import 'forge-std/Test.sol'; import { - IAccessModule, - IWhitelistAccessModule, - WhitelistAccessModule + IAccessModule, WhitelistAccessModule } from '../../../../contracts/examples/modules/access/WhitelistAccessModule.sol'; +import {Helpers} from '../../../utils/Helpers.sol'; contract ForTest_WhitelistAccessModule is WhitelistAccessModule { constructor(IOracle _oracle) WhitelistAccessModule(_oracle) {} @@ -36,8 +30,6 @@ contract BaseTest is Test, Helpers { function setUp() public { oracle = IOracle(makeAddr('Oracle')); - vm.etch(address(oracle), hex'069420'); - // whitelistAccessModule = new ForTest_WhitelistAccessModule(oracle); } } From d028cb7696b01f67b44925929cacb3aeca65a38f Mon Sep 17 00:00:00 2001 From: zorzal Date: Wed, 27 Nov 2024 08:16:38 -0500 Subject: [PATCH 4/6] feat: use return variable instead of explicit return --- .../contracts/examples/modules/access/WhitelistAccessModule.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solidity/contracts/examples/modules/access/WhitelistAccessModule.sol b/solidity/contracts/examples/modules/access/WhitelistAccessModule.sol index edb8564..d2cd181 100644 --- a/solidity/contracts/examples/modules/access/WhitelistAccessModule.sol +++ b/solidity/contracts/examples/modules/access/WhitelistAccessModule.sol @@ -17,7 +17,7 @@ contract WhitelistAccessModule is Module, IWhitelistAccessModule { /// @inheritdoc IModule function moduleName() external pure returns (string memory _moduleName) { - return 'WhitelistAccessModule'; + _moduleName = 'WhitelistAccessModule'; } /// @inheritdoc IAccessModule From f5071b337df594bb388d6c2e7a7cd52a68985e9c Mon Sep 17 00:00:00 2001 From: zorzal Date: Wed, 27 Nov 2024 09:08:21 -0500 Subject: [PATCH 5/6] docs: include IWhitelistAccessModule to the docs' summary --- docs/src/SUMMARY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index bdfd8cc..8b107d2 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -78,6 +78,8 @@ - [IValidator](solidity/interfaces/core/IValidator.sol/interface.IValidator.md) - [❱ examples](solidity/interfaces/examples/README.md) - [❱ modules](solidity/interfaces/examples/modules/README.md) + - [❱ access](solidity/interfaces/examples/modules/access/README.md) + - [IWhitelistAccessModule](solidity/interfaces/examples/modules/access/IWhitelistAccessModule.sol/interface.IWhitelistAccessModule.md) - [❱ dispute](solidity/interfaces/examples/modules/dispute/README.md) - [IBondedDisputeModule](solidity/interfaces/examples/modules/dispute/IBondedDisputeModule.sol/interface.IBondedDisputeModule.md) - [ICircuitResolverModule](solidity/interfaces/examples/modules/dispute/ICircuitResolverModule.sol/interface.ICircuitResolverModule.md) From e40b9caeaeb8a9a9576f2a929aeba6df5429e1e8 Mon Sep 17 00:00:00 2001 From: zorzal Date: Wed, 27 Nov 2024 09:35:31 -0500 Subject: [PATCH 6/6] docs: add WhitelistAccessModule to the AC doc as example --- docs/src/content/core/access_control.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/content/core/access_control.md b/docs/src/content/core/access_control.md index 58b3545..92e1988 100644 --- a/docs/src/content/core/access_control.md +++ b/docs/src/content/core/access_control.md @@ -19,3 +19,6 @@ Notice: This contract should NOT be used in the context of Modules as it does pr `OracleAccessController` introduces an easy to use modifier for the Oracle contract that validates authorization for interaction within the Oracle. See [IOracleAccessController.sol](/solidity/interfaces/core/access/IOracleAccessController.sol/interface.IOracleAccessController.md) for more details. + +## Examples +- [WhitelistAccessModule](/solidity/interfaces/examples/modules/access/IWhitelistAccessModule.sol/interface.IWhitelistAccessModule.md)