Skip to content

Commit

Permalink
feat: whitelist access module (#86)
Browse files Browse the repository at this point in the history
* feat: add WhitelistAccessModule, along with its interface

* test: add WhitelistAccessModule unit tests

* chore: lint

* feat: use return variable instead of explicit return

* docs: include IWhitelistAccessModule to the docs' summary

* docs: add WhitelistAccessModule to the AC doc as example
  • Loading branch information
xorsal authored Nov 28, 2024
1 parent 0eddb84 commit 483e92f
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions docs/src/content/core/access_control.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
@@ -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) {
_moduleName = '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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
114 changes: 114 additions & 0 deletions solidity/test/unit/modules/access/WhitelistAccessModule.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IOracle} from '@defi-wonderland/prophet-core/solidity/interfaces/IOracle.sol';
import 'forge-std/Test.sol';

import {
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) {}

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'));
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);
}
}

0 comments on commit 483e92f

Please sign in to comment.