Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: access control #44

Draft
wants to merge 6 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ multiline_func_header = 'params_first'
sort_imports = true

[profile.default]
solc_version = '0.8.19'
solc_version = '0.8.20'
src = 'solidity'
test = 'solidity/test'
out = 'out'
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"devDependencies": {
"@commitlint/cli": "17.0.3",
"@commitlint/config-conventional": "17.0.3",
"@openzeppelin/contracts": "^5.0.2",
"dotenv-cli": "7.2.1",
"ds-test": "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0",
"forge-std": "https://github.com/foundry-rs/forge-std.git#v1.7.3",
Expand All @@ -48,5 +49,6 @@
"solhint-plugin-defi-wonderland": "1.1.2",
"sort-package-json": "2.4.1",
"standard-version": "9.5.0"
}
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
44 changes: 44 additions & 0 deletions solidity/contracts/AccessController.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IAccessControlModule} from '../interfaces/modules/accessControl/IAccessControlModule.sol';

abstract contract AccessController {
/**
* @notice The access control struct
* @param user The address of the user
* @param data The data for access control validation
*/
struct AccessControl {
address user;
bytes data;
}

error AccessController_NoAccess();

/**
* @notice Modifier to check if the caller has access to the user
* @param _accessControlModule The access control module
* @param _accessControl The access control struct
*/
modifier hasAccess(
address _accessControlModule,
bytes32 _typehash,
bytes memory _params,
AccessControl memory _accessControl
) {
bool _hasAccess = msg.sender == _accessControl.user
|| (
_accessControlModule != address(0)
&& IAccessControlModule(_accessControlModule).hasAccess({
_caller: msg.sender,
_user: _accessControl.user,
_typehash: _typehash,
_params: _params,
_data: _accessControl.data
})
);
if (!_hasAccess) revert AccessController_NoAccess();
_;
}
}
81 changes: 55 additions & 26 deletions solidity/contracts/Oracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {IResolutionModule} from '../interfaces/modules/resolution/IResolutionMod
import {IResponseModule} from '../interfaces/modules/response/IResponseModule.sol';
import {ValidatorLib} from '../libraries/ValidatorLib.sol';

contract Oracle is IOracle {
import {AccessController} from './AccessController.sol';

import {OracleTypehash} from './OracleTypehash.sol';

contract Oracle is IOracle, AccessController, OracleTypehash {
using ValidatorLib for *;

/// @inheritdoc IOracle
Expand Down Expand Up @@ -57,20 +61,25 @@ contract Oracle is IOracle {
uint256 public totalRequestCount;

/// @inheritdoc IOracle
function createRequest(Request calldata _request, bytes32 _ipfsHash) external returns (bytes32 _requestId) {
_requestId = _createRequest(_request, _ipfsHash);
function createRequest(
Request calldata _request,
AccessControl memory _accessControl,
bytes32 _ipfsHash
) external returns (bytes32 _requestId) {
_requestId = _createRequest(_request, _ipfsHash, _accessControl);
}

/// @inheritdoc IOracle
function createRequests(
Request[] calldata _requestsData,
bytes32[] calldata _ipfsHashes
bytes32[] calldata _ipfsHashes,
AccessControl memory _accessControl
) external returns (bytes32[] memory _batchRequestsIds) {
uint256 _requestsAmount = _requestsData.length;
_batchRequestsIds = new bytes32[](_requestsAmount);

for (uint256 _i = 0; _i < _requestsAmount;) {
_batchRequestsIds[_i] = _createRequest(_requestsData[_i], _ipfsHashes[_i]);
_batchRequestsIds[_i] = _createRequest(_requestsData[_i], _ipfsHashes[_i], _accessControl);
unchecked {
++_i;
}
Expand Down Expand Up @@ -105,8 +114,13 @@ contract Oracle is IOracle {
/// @inheritdoc IOracle
function proposeResponse(
Request calldata _request,
Response calldata _response
) external returns (bytes32 _responseId) {
Response calldata _response,
AccessControl memory _accessControl
)
external
hasAccess(_request.accessControlModule, PROPOSE_TYPEHASH, abi.encode(_request, _response), _accessControl)
returns (bytes32 _responseId)
{
_responseId = ValidatorLib._validateResponse(_request, _response);

bytes32 _requestId = _response.requestId;
Expand All @@ -116,7 +130,7 @@ contract Oracle is IOracle {
}

// The caller must be the proposer, unless the response is coming from a dispute module
if (msg.sender != _response.proposer && msg.sender != address(_request.disputeModule)) {
if (_accessControl.user != _response.proposer && _accessControl.user != address(_request.disputeModule)) {
revert Oracle_InvalidResponseBody();
}

Expand All @@ -130,7 +144,7 @@ contract Oracle is IOracle {
}

_participants[_requestId] = abi.encodePacked(_participants[_requestId], _response.proposer);
IResponseModule(_request.responseModule).propose(_request, _response, msg.sender);
IResponseModule(_request.responseModule).propose(_request, _response, _accessControl.user);
_responseIds[_requestId] = abi.encodePacked(_responseIds[_requestId], _responseId);
responseCreatedAt[_responseId] = uint128(block.number);

Expand All @@ -141,8 +155,9 @@ contract Oracle is IOracle {
function disputeResponse(
Request calldata _request,
Response calldata _response,
Dispute calldata _dispute
) external returns (bytes32 _disputeId) {
Dispute calldata _dispute,
AccessControl memory _accessControl
) external hasAccess(_request.accessControlModule, msg.sender, _accessControl) returns (bytes32 _disputeId) {
bytes32 _responseId;
(_responseId, _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute);

Expand All @@ -156,7 +171,7 @@ contract Oracle is IOracle {
revert Oracle_InvalidDisputeBody();
}

if (_dispute.disputer != msg.sender) {
if (_dispute.disputer != _accessControl.user) {
revert Oracle_InvalidDisputeBody();
}

Expand All @@ -168,7 +183,7 @@ contract Oracle is IOracle {
revert Oracle_ResponseAlreadyDisputed(_responseId);
}

_participants[_requestId] = abi.encodePacked(_participants[_requestId], msg.sender);
_participants[_requestId] = abi.encodePacked(_participants[_requestId], _accessControl.user);
disputeStatus[_disputeId] = DisputeStatus.Active;
disputeOf[_responseId] = _disputeId;
disputeCreatedAt[_disputeId] = uint128(block.number);
Expand Down Expand Up @@ -200,6 +215,7 @@ contract Oracle is IOracle {
// Notify the dispute module about the escalation
IDisputeModule(_request.disputeModule).onDisputeStatusChange(_disputeId, _request, _response, _dispute);

// TODO: Let's remove the msg.sender from this event and we can remove the access control
emit DisputeEscalated(msg.sender, _disputeId, block.number);

if (address(_request.resolutionModule) != address(0)) {
Expand Down Expand Up @@ -232,6 +248,7 @@ contract Oracle is IOracle {

IResolutionModule(_request.resolutionModule).resolveDispute(_disputeId, _request, _response, _dispute);

// TODO: Same, remove the sender from this event. We don't really need it
emit DisputeResolved(_disputeId, _dispute, msg.sender, block.number);
}

Expand Down Expand Up @@ -301,7 +318,9 @@ contract Oracle is IOracle {
}

/// @inheritdoc IOracle
function getResponseIds(bytes32 _requestId) public view returns (bytes32[] memory _ids) {
function getResponseIds(
bytes32 _requestId
) public view returns (bytes32[] memory _ids) {
bytes memory _responses = _responseIds[_requestId];
uint256 _length = _responses.length / 32;

Expand All @@ -317,7 +336,11 @@ contract Oracle is IOracle {
}

/// @inheritdoc IOracle
function finalize(IOracle.Request calldata _request, IOracle.Response calldata _response) external {
function finalize(
IOracle.Request calldata _request,
IOracle.Response calldata _response,
AccessControl memory _accessControl
) external hasAccess(_request.accessControlModule, msg.sender, _accessControl) {
bytes32 _requestId;
bytes32 _responseId;

Expand All @@ -335,18 +358,18 @@ contract Oracle is IOracle {
finalizedAt[_requestId] = uint128(block.number);

if (address(_request.finalityModule) != address(0)) {
IFinalityModule(_request.finalityModule).finalizeRequest(_request, _response, msg.sender);
IFinalityModule(_request.finalityModule).finalizeRequest(_request, _response, _accessControl.user);
}

if (address(_request.resolutionModule) != address(0)) {
IResolutionModule(_request.resolutionModule).finalizeRequest(_request, _response, msg.sender);
IResolutionModule(_request.resolutionModule).finalizeRequest(_request, _response, _accessControl.user);
}

IDisputeModule(_request.disputeModule).finalizeRequest(_request, _response, msg.sender);
IResponseModule(_request.responseModule).finalizeRequest(_request, _response, msg.sender);
IRequestModule(_request.requestModule).finalizeRequest(_request, _response, msg.sender);
IDisputeModule(_request.disputeModule).finalizeRequest(_request, _response, _accessControl.user);
IResponseModule(_request.responseModule).finalizeRequest(_request, _response, _accessControl.user);
IRequestModule(_request.requestModule).finalizeRequest(_request, _response, _accessControl.user);

emit OracleRequestFinalized(_requestId, _responseId, msg.sender, block.number);
emit OracleRequestFinalized(_requestId, _responseId, _accessControl.user, block.number);
}

/**
Expand All @@ -355,7 +378,9 @@ contract Oracle is IOracle {
* @param _request The request to be finalized
* @return _requestId The id of the finalized request
*/
function _finalizeWithoutResponse(IOracle.Request calldata _request) internal view returns (bytes32 _requestId) {
function _finalizeWithoutResponse(
IOracle.Request calldata _request
) internal view returns (bytes32 _requestId) {
_requestId = ValidatorLib._getId(_request);

if (requestCreatedAt[_requestId] == 0) {
Expand Down Expand Up @@ -419,12 +444,16 @@ contract Oracle is IOracle {
* @param _ipfsHash The hashed IPFS CID of the metadata json
* @return _requestId The id of the created request
*/
function _createRequest(Request memory _request, bytes32 _ipfsHash) internal returns (bytes32 _requestId) {
function _createRequest(
Request memory _request,
bytes32 _ipfsHash,
AccessControl memory _accessControl
) internal hasAccess(_request.accessControlModule, msg.sender, _accessControl) returns (bytes32 _requestId) {
uint256 _requestNonce = totalRequestCount++;

if (_request.nonce == 0) _request.nonce = uint96(_requestNonce);

if (msg.sender != _request.requester || _requestNonce != _request.nonce) {
if (_accessControl.user != _request.requester || _requestNonce != _request.nonce) {
revert Oracle_InvalidRequestBody();
}

Expand All @@ -441,8 +470,8 @@ contract Oracle is IOracle {
_request.finalityModule
);

_participants[_requestId] = abi.encodePacked(_participants[_requestId], msg.sender);
IRequestModule(_request.requestModule).createRequest(_requestId, _request.requestModuleData, msg.sender);
_participants[_requestId] = abi.encodePacked(_participants[_requestId], _accessControl.user);
IRequestModule(_request.requestModule).createRequest(_requestId, _request.requestModuleData, _accessControl.user);

emit RequestCreated(_requestId, _request, _ipfsHash, block.number);
}
Expand Down
8 changes: 8 additions & 0 deletions solidity/contracts/OracleTypehash.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract OracleTypehash {
bytes32 internal constant PROPOSE_TYPEHASH = keccak256('ProposeResponse(Request _request, Response _response)');
bytes32 internal constant DISPUTE_TYPEHASH =
keccak256('DisputeResponse(Request _request, Response _response, Dispute _dispute,)');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
keccak256('DisputeResponse(Request _request, Response _response, Dispute _dispute,)');
keccak256('DisputeResponse(Request _request, Response _response, Dispute _dispute)');

i understand this is a WIP, but this one im pointing because it can be easy to miss

}
62 changes: 62 additions & 0 deletions solidity/contracts/PermitAccessControlModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IAccessControlModule} from '../interfaces/modules/accessControl/IAccessControlModule.sol';
import {Nonces} from '@openzeppelin/contracts/utils/Nonces.sol';

import {ECDSA} from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol';

contract PermitAccessControl is Nonces, EIP712 {
/**
* @notice The access control struct
* @param user The address of the user
* @param data The data for access control validation
*/
struct AccessControlData {
uint256 nonce;
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}

error ERC2612ExpiredSignature(uint256 deadline);
error ERC2612InvalidSigner(address signer, address owner);

/**
* @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
*
* It's a good idea to use the same `name` that is defined as the ERC-20 token name.
*/
constructor(
string memory name
) EIP712(name, '1') {}

function hasAccess(
address,
address _user,
bytes32 _signature,
bytes memory _params,
bytes calldata _data
) external returns (bool _hasAccess) {
AccessControlData memory _permit = abi.decode(_data, (AccessControlData));
// I don't think you care about validating the _caller
// You do care for repeatability, so a nonce is 100% necessary
// You care about expiration, so a deadline is 100% necessary
// You care abxout the function and the parameters that were approved
if (block.timestamp > _permit.deadline) {
revert ERC2612ExpiredSignature(_permit.deadline);
}
// signature, params (removing the last parameter for the access control), nonce, deadline
bytes32 structHash = keccak256(abi.encode(_signature, _params, _useNonce(_user), _permit.deadline));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add block.chainId for cross-chain, and something like address(this) - this last one not so sure about just want to make sure that this signature is not repeatable in another access control module?


bytes32 hash = _hashTypedDataV4(structHash);

address signer = ECDSA.recover(hash, _permit.v, _permit.r, _permit.s);

if (signer != _user) {
revert ERC2612InvalidSigner(signer, _user);
}
}
}
4 changes: 4 additions & 0 deletions solidity/interfaces/IOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ interface IOracle {
* @param disputeModule The address of the dispute module
* @param resolutionModule The address of the resolution module
* @param finalityModule The address of the finality module
* @param accessControlModule The address of the access control module
* // * @param accessControlModuleData The parameters for the access control module // TODO: Access control could be used to create a request so there is no way to have general data?
* @param requestModuleData The parameters for the request module
* @param responseModuleData The parameters for the response module
* @param disputeModuleData The parameters for the dispute module
Expand All @@ -209,6 +211,8 @@ interface IOracle {
address disputeModule;
address resolutionModule;
address finalityModule;
address accessControlModule;
bytes accessControlModuleData;
bytes requestModuleData;
bytes responseModuleData;
bytes disputeModuleData;
Expand Down
18 changes: 18 additions & 0 deletions solidity/interfaces/modules/accessControl/IAccessControlModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IModule} from '../../IModule.sol';

/**
* @title ResponseModule
* @notice Common interface for all response modules
*/
interface IAccessControlModule is IModule {
function hasAccess(
address _caller,
address _user,
bytes32 _typehash,
bytes memory _params,
bytes calldata _data
) external returns (bool _hasAccess);
}
Loading
Loading