From 05f517562f1709585d6da2d776f9f749d0ed774f Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:23:58 +0400 Subject: [PATCH 1/8] feat: uncomment helpers --- .../extensions/IAccountingExtension.sol | 380 +++++++++--------- solidity/test/utils/Helpers.sol | 124 +++--- 2 files changed, 258 insertions(+), 246 deletions(-) diff --git a/solidity/interfaces/extensions/IAccountingExtension.sol b/solidity/interfaces/extensions/IAccountingExtension.sol index 5a42243e..74c9c9cd 100644 --- a/solidity/interfaces/extensions/IAccountingExtension.sol +++ b/solidity/interfaces/extensions/IAccountingExtension.sol @@ -1,190 +1,190 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; - -// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; - -// /* -// * @title AccountingExtension -// * @notice Extension allowing users to deposit and bond funds -// * to be used for payments and disputes. -// */ -// interface IAccountingExtension { -// /*/////////////////////////////////////////////////////////////// -// EVENTS -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice A user deposited tokens into the accounting extension -// * @param _depositor The user who deposited the tokens -// * @param _token The address of the token deposited by the user -// * @param _amount The amount of `_token` deposited -// */ -// event Deposited(address indexed _depositor, IERC20 indexed _token, uint256 _amount); - -// /** -// * @notice A user withdrew tokens from the accounting extension -// * @param _withdrawer The user who withdrew the tokens -// * @param _token The address of the token withdrawn by the user -// * @param _amount The amount of `_token` withdrawn -// */ -// event Withdrew(address indexed _withdrawer, IERC20 indexed _token, uint256 _amount); - -// /** -// * @notice A payment between users has been made -// * @param _beneficiary The user receiving the tokens -// * @param _payer The user who is getting its tokens transferred -// * @param _token The address of the token being transferred -// * @param _amount The amount of `_token` transferred -// */ -// event Paid( -// bytes32 indexed _requestId, address indexed _beneficiary, address indexed _payer, IERC20 _token, uint256 _amount -// ); - -// /** -// * @notice User's funds have been bonded -// * @param _bonder The user who is getting its tokens bonded -// * @param _token The address of the token being bonded -// * @param _amount The amount of `_token` bonded -// */ -// event Bonded(bytes32 indexed _requestId, address indexed _bonder, IERC20 indexed _token, uint256 _amount); - -// /** -// * @notice User's funds have been released -// * @param _beneficiary The user who is getting its tokens released -// * @param _token The address of the token being released -// * @param _amount The amount of `_token` released -// */ -// event Released(bytes32 indexed _requestId, address indexed _beneficiary, IERC20 indexed _token, uint256 _amount); - -// /*/////////////////////////////////////////////////////////////// -// ERRORS -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice Thrown when the account doesn't have enough balance to bond/withdraw -// * or not enough bonded to release/pay -// */ -// error AccountingExtension_InsufficientFunds(); - -// /** -// * @notice Thrown when the module bonding user tokens hasn't been approved by the user. -// */ -// error AccountingExtension_InsufficientAllowance(); - -// /** -// * @notice Thrown when an `onlyAllowedModule` function is called by something -// * else than a module being used in the corresponding request -// */ -// error AccountingExtension_UnauthorizedModule(); - -// /** -// * @notice Thrown when an `onlyParticipant` function is called with an address -// * that is not part of the request. -// */ -// error AccountingExtension_UnauthorizedUser(); - -// /*/////////////////////////////////////////////////////////////// -// VARIABLES -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice Returns the interface for the Oracle contract -// */ -// function ORACLE() external view returns (IOracle _oracle); - -// /** -// * @notice Returns the amount of a token a user has bonded -// * @param _user The address of the user with bonded tokens -// * @param _bondToken The token bonded -// * @param _requestId The id of the request the user bonded for -// * @return _amount The amount of `_bondToken` bonded -// */ -// function bondedAmountOf(address _user, IERC20 _bondToken, bytes32 _requestId) external returns (uint256 _amount); - -// /** -// * @notice Returns the amount of a token a user has deposited -// * @param _user The address of the user with deposited tokens -// * @param _token The token deposited -// * @return _amount The amount of `_token` deposited -// */ -// function balanceOf(address _user, IERC20 _token) external view returns (uint256 _amount); - -// /*/////////////////////////////////////////////////////////////// -// LOGIC -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice Transfers tokens from a user and updates his virtual balance -// * @dev The user must have approved the accounting extension to transfer the tokens. -// * @param _token The address of the token being deposited -// * @param _amount The amount of `_token` to deposit -// */ -// function deposit(IERC20 _token, uint256 _amount) external; - -// /** -// * @notice Allows an user to withdraw deposited tokens -// * @param _token The address of the token being withdrawn -// * @param _amount The amount of `_token` to withdraw -// */ -// function withdraw(IERC20 _token, uint256 _amount) external; - -// /** -// * @notice Allows a allowed module to transfer bonded tokens from one user to another -// * @dev Only the virtual balances in the accounting extension are modified. The token contract -// * is not called nor its balances modified. -// * @param _requestId The id of the request handling the user's tokens -// * @param _payer The address of the user paying the tokens -// * @param _receiver The address of the user receiving the tokens -// * @param _token The address of the token being transferred -// * @param _amount The amount of `_token` being transferred -// */ -// function pay(bytes32 _requestId, address _payer, address _receiver, IERC20 _token, uint256 _amount) external; - -// /** -// * @notice Allows a allowed module to bond a user's tokens for a request -// * @param _bonder The address of the user to bond tokens for -// * @param _requestId The id of the request the user is bonding for -// * @param _token The address of the token being bonded -// * @param _amount The amount of `_token` to bond -// */ -// function bond(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount) external; - -// /** -// * @notice Allows a valid module to bond a user's tokens for a request -// * @param _bonder The address of the user to bond tokens for -// * @param _requestId The id of the request the user is bonding for -// * @param _token The address of the token being bonded -// * @param _amount The amount of `_token` to bond -// * @param _sender The address starting the propose call on the Oracle -// */ -// function bond(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount, address _sender) external; - -// /** -// * @notice Allows a valid module to release a user's tokens -// * @param _bonder The address of the user to release tokens for -// * @param _requestId The id of the request where the tokens were bonded -// * @param _token The address of the token being released -// * @param _amount The amount of `_token` to release -// */ -// function release(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount) external; - -// /** -// * @notice Allows a user to approve a module for bonding tokens -// * @param _module The address of the module to be approved -// */ -// function approveModule(address _module) external; - -// /** -// * @notice Allows a user to revoke a module's approval for bonding tokens -// * @param _module The address of the module to be revoked -// */ -// function revokeModule(address _module) external; - -// /** -// * @notice Returns a list of all modules a user has approved -// * @param _user The address of the user -// * @return _approvedModules The array of all modules approved by the user -// */ -// function approvedModules(address _user) external view returns (address[] memory _approvedModules); -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; + +/* + * @title AccountingExtension + * @notice Extension allowing users to deposit and bond funds + * to be used for payments and disputes. + */ +interface IAccountingExtension { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice A user deposited tokens into the accounting extension + * @param _depositor The user who deposited the tokens + * @param _token The address of the token deposited by the user + * @param _amount The amount of `_token` deposited + */ + event Deposited(address indexed _depositor, IERC20 indexed _token, uint256 _amount); + + /** + * @notice A user withdrew tokens from the accounting extension + * @param _withdrawer The user who withdrew the tokens + * @param _token The address of the token withdrawn by the user + * @param _amount The amount of `_token` withdrawn + */ + event Withdrew(address indexed _withdrawer, IERC20 indexed _token, uint256 _amount); + + /** + * @notice A payment between users has been made + * @param _beneficiary The user receiving the tokens + * @param _payer The user who is getting its tokens transferred + * @param _token The address of the token being transferred + * @param _amount The amount of `_token` transferred + */ + event Paid( + bytes32 indexed _requestId, address indexed _beneficiary, address indexed _payer, IERC20 _token, uint256 _amount + ); + + /** + * @notice User's funds have been bonded + * @param _bonder The user who is getting its tokens bonded + * @param _token The address of the token being bonded + * @param _amount The amount of `_token` bonded + */ + event Bonded(bytes32 indexed _requestId, address indexed _bonder, IERC20 indexed _token, uint256 _amount); + + /** + * @notice User's funds have been released + * @param _beneficiary The user who is getting its tokens released + * @param _token The address of the token being released + * @param _amount The amount of `_token` released + */ + event Released(bytes32 indexed _requestId, address indexed _beneficiary, IERC20 indexed _token, uint256 _amount); + + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Thrown when the account doesn't have enough balance to bond/withdraw + * or not enough bonded to release/pay + */ + error AccountingExtension_InsufficientFunds(); + + /** + * @notice Thrown when the module bonding user tokens hasn't been approved by the user. + */ + error AccountingExtension_InsufficientAllowance(); + + /** + * @notice Thrown when an `onlyAllowedModule` function is called by something + * else than a module being used in the corresponding request + */ + error AccountingExtension_UnauthorizedModule(); + + /** + * @notice Thrown when an `onlyParticipant` function is called with an address + * that is not part of the request. + */ + error AccountingExtension_UnauthorizedUser(); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Returns the interface for the Oracle contract + */ + function ORACLE() external view returns (IOracle _oracle); + + /** + * @notice Returns the amount of a token a user has bonded + * @param _user The address of the user with bonded tokens + * @param _bondToken The token bonded + * @param _requestId The id of the request the user bonded for + * @return _amount The amount of `_bondToken` bonded + */ + function bondedAmountOf(address _user, IERC20 _bondToken, bytes32 _requestId) external returns (uint256 _amount); + + /** + * @notice Returns the amount of a token a user has deposited + * @param _user The address of the user with deposited tokens + * @param _token The token deposited + * @return _amount The amount of `_token` deposited + */ + function balanceOf(address _user, IERC20 _token) external view returns (uint256 _amount); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Transfers tokens from a user and updates his virtual balance + * @dev The user must have approved the accounting extension to transfer the tokens. + * @param _token The address of the token being deposited + * @param _amount The amount of `_token` to deposit + */ + function deposit(IERC20 _token, uint256 _amount) external; + + /** + * @notice Allows an user to withdraw deposited tokens + * @param _token The address of the token being withdrawn + * @param _amount The amount of `_token` to withdraw + */ + function withdraw(IERC20 _token, uint256 _amount) external; + + /** + * @notice Allows a allowed module to transfer bonded tokens from one user to another + * @dev Only the virtual balances in the accounting extension are modified. The token contract + * is not called nor its balances modified. + * @param _requestId The id of the request handling the user's tokens + * @param _payer The address of the user paying the tokens + * @param _receiver The address of the user receiving the tokens + * @param _token The address of the token being transferred + * @param _amount The amount of `_token` being transferred + */ + function pay(bytes32 _requestId, address _payer, address _receiver, IERC20 _token, uint256 _amount) external; + + /** + * @notice Allows a allowed module to bond a user's tokens for a request + * @param _bonder The address of the user to bond tokens for + * @param _requestId The id of the request the user is bonding for + * @param _token The address of the token being bonded + * @param _amount The amount of `_token` to bond + */ + function bond(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount) external; + + /** + * @notice Allows a valid module to bond a user's tokens for a request + * @param _bonder The address of the user to bond tokens for + * @param _requestId The id of the request the user is bonding for + * @param _token The address of the token being bonded + * @param _amount The amount of `_token` to bond + * @param _sender The address starting the propose call on the Oracle + */ + function bond(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount, address _sender) external; + + /** + * @notice Allows a valid module to release a user's tokens + * @param _bonder The address of the user to release tokens for + * @param _requestId The id of the request where the tokens were bonded + * @param _token The address of the token being released + * @param _amount The amount of `_token` to release + */ + function release(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount) external; + + /** + * @notice Allows a user to approve a module for bonding tokens + * @param _module The address of the module to be approved + */ + function approveModule(address _module) external; + + /** + * @notice Allows a user to revoke a module's approval for bonding tokens + * @param _module The address of the module to be revoked + */ + function revokeModule(address _module) external; + + /** + * @notice Returns a list of all modules a user has approved + * @param _user The address of the user + * @return _approvedModules The array of all modules approved by the user + */ + function approvedModules(address _user) external view returns (address[] memory _approvedModules); +} diff --git a/solidity/test/utils/Helpers.sol b/solidity/test/utils/Helpers.sol index 46874489..5bddd4bc 100644 --- a/solidity/test/utils/Helpers.sol +++ b/solidity/test/utils/Helpers.sol @@ -1,56 +1,68 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; - -// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -// import {DSTestPlus} from '@defi-wonderland/solidity-utils/solidity/test/DSTestPlus.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; - -// import {IAccountingExtension} from '../../interfaces/extensions/IAccountingExtension.sol'; - -// contract Helpers is DSTestPlus { -// modifier assumeFuzzable(address _address) { -// _assumeFuzzable(_address); -// _; -// } - -// function _assumeFuzzable(address _address) internal pure { -// assumeNotForgeAddress(_address); -// assumeNotZeroAddress(_address); -// assumeNotPrecompile(_address); -// } - -// function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { -// vm.mockCall(_receiver, _calldata, _returned); -// vm.expectCall(_receiver, _calldata); -// } - -// function _getMockDispute( -// bytes32 _requestId, -// address _disputer, -// address _proposer -// ) internal view returns (IOracle.Dispute memory _dispute) { -// _dispute = IOracle.Dispute({ -// disputer: _disputer, -// responseId: bytes32('response'), -// proposer: _proposer, -// requestId: _requestId, -// status: IOracle.DisputeStatus.None, -// createdAt: block.timestamp -// }); -// } - -// function _forBondDepositERC20( -// IAccountingExtension _accountingExtension, -// address _depositor, -// IERC20 _token, -// uint256 _depositAmount, -// uint256 _balanceIncrease -// ) internal { -// vm.assume(_balanceIncrease >= _depositAmount); -// deal(address(_token), _depositor, _balanceIncrease); -// vm.startPrank(_depositor); -// _token.approve(address(_accountingExtension), _depositAmount); -// _accountingExtension.deposit(_token, _depositAmount); -// vm.stopPrank(); -// } -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {DSTestPlus} from '@defi-wonderland/solidity-utils/solidity/test/DSTestPlus.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; + +import {IAccountingExtension} from '../../interfaces/extensions/IAccountingExtension.sol'; + +contract Helpers is DSTestPlus { + modifier assumeFuzzable(address _address) { + _assumeFuzzable(_address); + _; + } + + function _assumeFuzzable(address _address) internal pure { + assumeNotForgeAddress(_address); + assumeNotZeroAddress(_address); + assumeNotPrecompile(_address); + } + + function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { + vm.mockCall(_receiver, _calldata, _returned); + vm.expectCall(_receiver, _calldata); + } + + function _getMockDispute( + bytes32 _requestId, + address _disputer, + address _proposer + ) internal view returns (IOracle.Dispute memory _dispute) { + _dispute = IOracle.Dispute({ + disputer: _disputer, + responseId: bytes32('response'), + proposer: _proposer, + requestId: _requestId, + status: IOracle.DisputeStatus.None, + createdAt: block.timestamp + }); + } + + function _forBondDepositERC20( + IAccountingExtension _accountingExtension, + address _depositor, + IERC20 _token, + uint256 _depositAmount, + uint256 _balanceIncrease + ) internal { + vm.assume(_balanceIncrease >= _depositAmount); + deal(address(_token), _depositor, _balanceIncrease); + vm.startPrank(_depositor); + _token.approve(address(_accountingExtension), _depositAmount); + _accountingExtension.deposit(_token, _depositAmount); + vm.stopPrank(); + } + + function _getId(IOracle.Response memory _response) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_response)); + } + + function _getId(IOracle.Request memory _request) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_request)); + } + + function _getId(IOracle.Dispute memory _dispute) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_dispute)); + } +} From ba18258edfbf77ec83eba749667ce43a8b939c4b Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:19:43 +0400 Subject: [PATCH 2/8] perf: optimize `HttpRequestModule` --- .../modules/request/http_request_module.md | 4 +- .../modules/request/HttpRequestModule.sol | 125 +++-- .../modules/request/IHttpRequestModule.sol | 124 +++-- .../modules/request/HttpRequestModule.t.sol | 498 +++++++----------- 4 files changed, 330 insertions(+), 421 deletions(-) diff --git a/docs/src/content/modules/request/http_request_module.md b/docs/src/content/modules/request/http_request_module.md index 7b4a8ebf..332e16d5 100644 --- a/docs/src/content/modules/request/http_request_module.md +++ b/docs/src/content/modules/request/http_request_module.md @@ -10,8 +10,8 @@ The `HttpRequestModule` is a contract that allows users to request HTTP calls. ### Key Methods -- `decodeRequestData(bytes32 _requestId)`: This method decodes the data for a request given its ID. It returns the URL, HTTP method, body, accounting extension, payment token, and payment amount associated with the request. -- `finalizeRequest(bytes32 _requestId, address)`: This method finalizes a request by paying the proposer if there is a valid response, or releases the requester bond if no valid response was provided. +- `decodeRequestData(bytes calldata _data)`: This method decodes the data for a request given its ID. It returns the URL, HTTP method, body, accounting extension, payment token, and payment amount associated with the request. +- `finalizeRequest`: This method finalizes a request by paying the proposer if there is a valid response, or releases the requester bond if no valid response was provided. ### Request Parameters diff --git a/solidity/contracts/modules/request/HttpRequestModule.sol b/solidity/contracts/modules/request/HttpRequestModule.sol index ef8890fa..ad5324fc 100644 --- a/solidity/contracts/modules/request/HttpRequestModule.sol +++ b/solidity/contracts/modules/request/HttpRequestModule.sol @@ -1,63 +1,62 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; - -// // solhint-disable-next-line no-unused-import -// import {Module, IModule} from '@defi-wonderland/prophet-core-contracts/solidity/contracts/Module.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; - -// import {IHttpRequestModule} from '../../../interfaces/modules/request/IHttpRequestModule.sol'; - -// contract HttpRequestModule is Module, IHttpRequestModule { -// constructor(IOracle _oracle) Module(_oracle) {} - -// /// @inheritdoc IModule -// function moduleName() public pure returns (string memory _moduleName) { -// _moduleName = 'HttpRequestModule'; -// } - -// /// @inheritdoc IHttpRequestModule -// function decodeRequestData(bytes32 _requestId) public view returns (RequestParameters memory _params) { -// _params = abi.decode(requestData[_requestId], (RequestParameters)); -// } - -// /** -// * @notice Bonds the requester tokens to use as payment for the response proposer. -// */ -// function _afterSetupRequest(bytes32 _requestId, bytes calldata) internal override { -// RequestParameters memory _params = decodeRequestData(_requestId); -// IOracle.Request memory _request = ORACLE.getRequest(_requestId); -// _params.accountingExtension.bond({ -// _bonder: _request.requester, -// _requestId: _requestId, -// _token: _params.paymentToken, -// _amount: _params.paymentAmount -// }); -// } - -// /// @inheritdoc IHttpRequestModule -// function finalizeRequest( -// bytes32 _requestId, -// address _finalizer -// ) external override(IHttpRequestModule, Module) onlyOracle { -// IOracle.Request memory _request = ORACLE.getRequest(_requestId); -// IOracle.Response memory _response = ORACLE.getFinalizedResponse(_requestId); -// RequestParameters memory _params = decodeRequestData(_requestId); -// if (_response.createdAt != 0) { -// _params.accountingExtension.pay({ -// _requestId: _requestId, -// _payer: _request.requester, -// _receiver: _response.proposer, -// _token: _params.paymentToken, -// _amount: _params.paymentAmount -// }); -// } else { -// _params.accountingExtension.release({ -// _bonder: _request.requester, -// _requestId: _requestId, -// _token: _params.paymentToken, -// _amount: _params.paymentAmount -// }); -// } -// emit RequestFinalized(_requestId, _finalizer); -// } -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +// solhint-disable-next-line no-unused-import +import {Module, IModule} from '@defi-wonderland/prophet-core-contracts/solidity/contracts/Module.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; + +import {IHttpRequestModule} from '../../../interfaces/modules/request/IHttpRequestModule.sol'; + +contract HttpRequestModule is Module, IHttpRequestModule { + constructor(IOracle _oracle) Module(_oracle) {} + + /// @inheritdoc IModule + function moduleName() public pure returns (string memory _moduleName) { + _moduleName = 'HttpRequestModule'; + } + + /// @inheritdoc IHttpRequestModule + function decodeRequestData(bytes calldata _data) public pure returns (RequestParameters memory _params) { + _params = abi.decode(_data, (RequestParameters)); + } + + /// @inheritdoc IHttpRequestModule + function createRequest(bytes32 _requestId, bytes calldata _data, address _requester) external { + RequestParameters memory _params = decodeRequestData(_data); + + _params.accountingExtension.bond({ + _bonder: _requester, + _requestId: _requestId, + _token: _params.paymentToken, + _amount: _params.paymentAmount + }); + } + + /// @inheritdoc IHttpRequestModule + function finalizeRequest( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + address _finalizer + ) external override(IHttpRequestModule, Module) onlyOracle { + RequestParameters memory _params = decodeRequestData(_request.requestModuleData); + + if (ORACLE.createdAt(_getId(_response)) != 0) { + _params.accountingExtension.pay({ + _requestId: _response.requestId, + _payer: _request.requester, + _receiver: _response.proposer, + _token: _params.paymentToken, + _amount: _params.paymentAmount + }); + } else { + _params.accountingExtension.release({ + _bonder: _request.requester, + _requestId: _response.requestId, + _token: _params.paymentToken, + _amount: _params.paymentAmount + }); + } + + emit RequestFinalized(_response.requestId, _response, _finalizer); + } +} diff --git a/solidity/interfaces/modules/request/IHttpRequestModule.sol b/solidity/interfaces/modules/request/IHttpRequestModule.sol index 5c28a4c4..9a01a797 100644 --- a/solidity/interfaces/modules/request/IHttpRequestModule.sol +++ b/solidity/interfaces/modules/request/IHttpRequestModule.sol @@ -1,65 +1,75 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; -// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -// import {IRequestModule} from -// '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/request/IRequestModule.sol'; -// import {IAccountingExtension} from '../../../interfaces/extensions/IAccountingExtension.sol'; +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; +import {IRequestModule} from + '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/request/IRequestModule.sol'; +import {IAccountingExtension} from '../../../interfaces/extensions/IAccountingExtension.sol'; -// /* -// * @title HttpRequestModule -// * @notice Module allowing users to request HTTP calls -// */ -// interface IHttpRequestModule is IRequestModule { -// /*/////////////////////////////////////////////////////////////// -// STRUCTS -// //////////////////////////////////////////////////////////////*/ +/* + * @title HttpRequestModule + * @notice Module allowing users to request HTTP calls + */ +interface IHttpRequestModule is IRequestModule { + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Parameters of the request as stored in the module -// * @param url The url to make the request to -// * @param method The HTTP method to use for the request -// * @param body The HTTP body to use for the request -// * @param accountingExtension The accounting extension used to bond and release tokens -// * @param paymentToken The token used to pay for the request -// * @param paymentAmount The amount of tokens to pay for the request -// */ -// struct RequestParameters { -// string url; -// string body; -// HttpMethod method; -// IAccountingExtension accountingExtension; -// IERC20 paymentToken; -// uint256 paymentAmount; -// } + /** + * @notice Parameters of the request as stored in the module + * @param url The url to make the request to + * @param method The HTTP method to use for the request + * @param body The HTTP body to use for the request + * @param accountingExtension The accounting extension used to bond and release tokens + * @param paymentToken The token used to pay for the request + * @param paymentAmount The amount of tokens to pay for the request + */ + struct RequestParameters { + string url; + string body; + HttpMethod method; + IAccountingExtension accountingExtension; + IERC20 paymentToken; + uint256 paymentAmount; + } -// /*/////////////////////////////////////////////////////////////// -// ENUMS -// //////////////////////////////////////////////////////////////*/ + /*/////////////////////////////////////////////////////////////// + ENUMS + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Available HTTP methods -// */ -// enum HttpMethod { -// GET, -// POST -// } + /** + * @notice Available HTTP methods + */ + enum HttpMethod { + GET, + POST + } -// /*/////////////////////////////////////////////////////////////// -// LOGIC -// //////////////////////////////////////////////////////////////*/ + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Returns the decoded data for a request -// * @param _requestId The ID of the request -// * @return _params The struct containing the parameters for the request -// */ -// function decodeRequestData(bytes32 _requestId) external view returns (RequestParameters memory _params); + /** + * @notice Returns the decoded data for a request + * @param _data The encoded request parameters + * @return _params The struct containing the parameters for the request + */ + function decodeRequestData(bytes calldata _data) external view returns (RequestParameters memory _params); -// /** -// * @notice Finalizes a request by paying the proposer if there is a valid response -// * or releases the requester bond if no valid response was provided -// * @param _requestId The ID of the request -// */ -// function finalizeRequest(bytes32 _requestId, address) external; -// } + /// @inheritdoc IRequestModule + function createRequest(bytes32 _requestId, bytes calldata _data, address _requester) external; + + /** + * @notice Finalizes the request by paying the proposer for the response or releasing the requester's bond if no response was submitted + * + * @param _request The request that is being finalized + * @param _response The final response + * @param _finalizer The user who triggered the finalization + */ + function finalizeRequest( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + address _finalizer + ) external; +} diff --git a/solidity/test/unit/modules/request/HttpRequestModule.t.sol b/solidity/test/unit/modules/request/HttpRequestModule.t.sol index a053e9fe..e7e300e7 100644 --- a/solidity/test/unit/modules/request/HttpRequestModule.t.sol +++ b/solidity/test/unit/modules/request/HttpRequestModule.t.sol @@ -1,299 +1,199 @@ -// // SPDX-License-Identifier: AGPL-3.0-only -// pragma solidity ^0.8.19; - -// import 'forge-std/Test.sol'; - -// import {Helpers} from '../../../utils/Helpers.sol'; - -// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; -// import {IModule} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IModule.sol'; - -// import {HttpRequestModule, IHttpRequestModule} from '../../../../contracts/modules/request/HttpRequestModule.sol'; - -// import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountingExtension.sol'; - -// /** -// * @dev Harness to set an entry in the requestData mapping, without triggering setup request hooks -// */ - -// contract ForTest_HttpRequestModule is HttpRequestModule { -// constructor(IOracle _oracle) HttpRequestModule(_oracle) {} - -// function forTest_setRequestData(bytes32 _requestId, bytes memory _data) public { -// requestData[_requestId] = _data; -// } -// } - -// /** -// * @title HTTP Request Module Unit tests -// */ -// contract BaseTest is Test, Helpers { -// // Mock request data -// string public constant URL = 'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd'; -// IHttpRequestModule.HttpMethod public constant METHOD = IHttpRequestModule.HttpMethod.GET; -// string public constant BODY = '69420'; - -// // Mock token -// IERC20 public immutable TOKEN = IERC20(makeAddr('ERC20')); -// // The target contract -// ForTest_HttpRequestModule public httpRequestModule; -// // A mock oracle -// IOracle public oracle; -// // A mock accounting extension -// IAccountingExtension public accounting = IAccountingExtension(makeAddr('accounting')); - -// event RequestFinalized(bytes32 indexed _requestId, address _finalizer); - -// /** -// * @notice Deploy the target and mock oracle+accounting extension -// */ -// function setUp() public { -// oracle = IOracle(makeAddr('Oracle')); -// vm.etch(address(oracle), hex'069420'); - -// accounting = IAccountingExtension(makeAddr('AccountingExtension')); -// vm.etch(address(accounting), hex'069420'); - -// httpRequestModule = new ForTest_HttpRequestModule(oracle); -// } -// } - -// contract HttpRequestModule_Unit_ModuleData is BaseTest { -// /** -// * @notice Test that the moduleName function returns the correct name -// */ -// function test_moduleNameReturnsName() public { -// assertEq(httpRequestModule.moduleName(), 'HttpRequestModule'); -// } - -// /** -// * @notice Test that the decodeRequestData function returns the correct values -// */ -// function test_decodeRequestData(bytes32 _requestId, uint256 _amount, IERC20 _token) public { -// bytes memory _requestData = abi.encode( -// IHttpRequestModule.RequestParameters({ -// url: URL, -// method: METHOD, -// body: BODY, -// accountingExtension: accounting, -// paymentToken: _token, -// paymentAmount: _amount -// }) -// ); - -// // Set the request data -// httpRequestModule.forTest_setRequestData(_requestId, _requestData); - -// // Decode the given request data -// IHttpRequestModule.RequestParameters memory _params = httpRequestModule.decodeRequestData(_requestId); - -// // Check: decoded values match original values? -// assertEq(_params.url, URL); -// assertEq(uint256(_params.method), uint256(METHOD)); -// assertEq(_params.body, BODY); -// assertEq(address(_params.accountingExtension), address(accounting)); -// assertEq(address(_params.paymentToken), address(_token)); -// assertEq(_params.paymentAmount, _amount); -// } -// } - -// contract HttpRequestModule_Unit_Setup is BaseTest { -// /** -// * @notice Test that the afterSetupRequest hook: -// * - decodes the request data -// * - gets the request from the oracle -// * - calls the bond function on the accounting extension -// */ -// function test_afterSetupRequestTriggered( -// bytes32 _requestId, -// address _requester, -// uint256 _amount, -// IERC20 _token -// ) public { -// bytes memory _requestData = abi.encode( -// IHttpRequestModule.RequestParameters({ -// url: URL, -// method: METHOD, -// body: BODY, -// accountingExtension: accounting, -// paymentToken: _token, -// paymentAmount: _amount -// }) -// ); - -// IOracle.Request memory _fullRequest; -// _fullRequest.requester = _requester; - -// // Mock and expect IOracle.getRequest to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getRequest, (_requestId)), abi.encode(_fullRequest)); - -// // Mock and expect IAccountingExtension.bond to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeWithSignature('bond(address,bytes32,address,uint256)', _requester, _requestId, _token, _amount), -// abi.encode(true) -// ); - -// vm.prank(address(oracle)); -// httpRequestModule.setupRequest(_requestId, _requestData); -// } -// } - -// contract HttpRequestModule_Unit_FinalizeRequest is BaseTest { -// /** -// * @notice Test that finalizeRequest calls: -// * - oracle get request -// * - oracle get response -// * - accounting extension pay -// * - accounting extension release -// */ -// function test_makesCalls( -// bytes32 _requestId, -// address _requester, -// address _proposer, -// uint256 _amount, -// IERC20 _token -// ) public { -// _amount = bound(_amount, 0, type(uint248).max); - -// // Use the correct accounting parameters -// bytes memory _requestData = abi.encode( -// IHttpRequestModule.RequestParameters({ -// url: URL, -// method: METHOD, -// body: BODY, -// accountingExtension: accounting, -// paymentToken: _token, -// paymentAmount: _amount -// }) -// ); - -// IOracle.Request memory _fullRequest; -// _fullRequest.requester = _requester; - -// IOracle.Response memory _fullResponse; -// _fullResponse.proposer = _proposer; -// _fullResponse.createdAt = block.timestamp; - -// // Set the request data -// httpRequestModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock and expect IOracle.getRequest to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getRequest, (_requestId)), abi.encode(_fullRequest)); - -// // Mock and expect IOracle.getFinalizedResponse to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, (_requestId)), abi.encode(_fullResponse) -// ); - -// // Mock and expect IAccountingExtension.pay to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.pay, (_requestId, _requester, _proposer, _token, _amount)), -// abi.encode() -// ); - -// vm.startPrank(address(oracle)); -// httpRequestModule.finalizeRequest(_requestId, address(oracle)); - -// // Test the release flow -// _fullResponse.createdAt = 0; - -// // Update mock call to return the response with createdAt = 0 -// _mockAndExpect( -// address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, (_requestId)), abi.encode(_fullResponse) -// ); - -// // Mock and expect IAccountingExtension.release to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.release, (_requester, _requestId, _token, _amount)), -// abi.encode(true) -// ); - -// httpRequestModule.finalizeRequest(_requestId, address(this)); -// } - -// function test_emitsEvent( -// bytes32 _requestId, -// address _requester, -// address _proposer, -// uint256 _amount, -// IERC20 _token -// ) public { -// // Use the correct accounting parameters -// bytes memory _requestData = abi.encode( -// IHttpRequestModule.RequestParameters({ -// url: URL, -// method: METHOD, -// body: BODY, -// accountingExtension: accounting, -// paymentToken: _token, -// paymentAmount: _amount -// }) -// ); - -// IOracle.Request memory _fullRequest; -// _fullRequest.requester = _requester; - -// IOracle.Response memory _fullResponse; -// _fullResponse.proposer = _proposer; -// _fullResponse.createdAt = block.timestamp; - -// // Set the request data -// httpRequestModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock and expect IOracle.getRequest to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getRequest, (_requestId)), abi.encode(_fullRequest)); - -// // Mock and expect IOracle.getFinalizedResponse to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, (_requestId)), abi.encode(_fullResponse) -// ); - -// // Mock and expect IAccountingExtension.pay to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.pay, (_requestId, _requester, _proposer, _token, _amount)), -// abi.encode() -// ); - -// vm.startPrank(address(oracle)); -// httpRequestModule.finalizeRequest(_requestId, address(oracle)); - -// // Test the release flow -// _fullResponse.createdAt = 0; - -// // Update mock call to return the response with createdAt = 0 -// _mockAndExpect( -// address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, (_requestId)), abi.encode(_fullResponse) -// ); - -// // Mock and expect IAccountingExtension.release to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.release, (_requester, _requestId, _token, _amount)), -// abi.encode(true) -// ); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(httpRequestModule)); -// emit RequestFinalized(_requestId, address(this)); - -// httpRequestModule.finalizeRequest(_requestId, address(this)); -// } - -// /** -// * @notice Test that the finalizeRequest reverts if caller is not the oracle -// */ -// function test_revertsIfWrongCaller(bytes32 _requestId, address _caller) public { -// vm.assume(_caller != address(oracle)); - -// // Check: does it revert if not called by the Oracle? -// vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); - -// vm.prank(_caller); -// httpRequestModule.finalizeRequest(_requestId, address(_caller)); -// } -// } +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; + +import 'forge-std/Test.sol'; + +import {Helpers} from '../../../utils/Helpers.sol'; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; +import {IModule} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IModule.sol'; + +import {HttpRequestModule, IHttpRequestModule} from '../../../../contracts/modules/request/HttpRequestModule.sol'; + +import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountingExtension.sol'; + +/** + * @title HTTP Request Module Unit tests + */ +contract BaseTest is Test, Helpers { + // Mock request data + string public constant URL = 'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd'; + IHttpRequestModule.HttpMethod public constant METHOD = IHttpRequestModule.HttpMethod.GET; + string public constant BODY = '69420'; + + // Mock token + IERC20 public immutable TOKEN = IERC20(makeAddr('ERC20')); + // The target contract + HttpRequestModule public httpRequestModule; + // A mock oracle + IOracle public oracle; + // A mock accounting extension + IAccountingExtension public accounting = IAccountingExtension(makeAddr('accounting')); + IOracle.Response public mockResponse; + address internal _proposer = makeAddr('proposer'); + bytes32 public mockId = bytes32('69'); + + event RequestFinalized(bytes32 indexed _requestId, address _finalizer); + + /** + * @notice Deploy the target and mock oracle+accounting extension + */ + function setUp() public { + oracle = IOracle(makeAddr('Oracle')); + vm.etch(address(oracle), hex'069420'); + + accounting = IAccountingExtension(makeAddr('AccountingExtension')); + vm.etch(address(accounting), hex'069420'); + + httpRequestModule = new HttpRequestModule(oracle); + mockResponse = IOracle.Response({proposer: _proposer, requestId: mockId, response: bytes('')}); + } +} + +contract HttpRequestModule_Unit_ModuleData is BaseTest { + /** + * @notice Test that the moduleName function returns the correct name + */ + function test_moduleNameReturnsName() public { + assertEq(httpRequestModule.moduleName(), 'HttpRequestModule'); + } + + /** + * @notice Test that the decodeRequestData function returns the correct values + */ + function test_decodeRequestData(bytes32 _requestId, uint256 _amount, IERC20 _token) public { + bytes memory _requestData = abi.encode( + IHttpRequestModule.RequestParameters({ + url: URL, + method: METHOD, + body: BODY, + accountingExtension: accounting, + paymentToken: _token, + paymentAmount: _amount + }) + ); + + // Decode the given request data + IHttpRequestModule.RequestParameters memory _params = httpRequestModule.decodeRequestData(_requestData); + + // Check: decoded values match original values? + assertEq(_params.url, URL); + assertEq(uint256(_params.method), uint256(METHOD)); + assertEq(_params.body, BODY); + assertEq(address(_params.accountingExtension), address(accounting)); + assertEq(address(_params.paymentToken), address(_token)); + assertEq(_params.paymentAmount, _amount); + } +} + +contract HttpRequestModule_Unit_FinalizeRequest is BaseTest { + /** + * @notice Test that finalizeRequest calls: + * - oracle get request + * - oracle get response + * - accounting extension pay + * - accounting extension release + */ + function test_makesCalls( + bytes32 _requestId, + address _requester, + address _proposer, + uint256 _amount, + IERC20 _token, + IOracle.Request calldata _request + ) public { + _amount = bound(_amount, 0, type(uint248).max); + + // Use the correct accounting parameters + bytes memory _requestData = abi.encode( + IHttpRequestModule.RequestParameters({ + url: URL, + method: METHOD, + body: BODY, + accountingExtension: accounting, + paymentToken: _token, + paymentAmount: _amount + }) + ); + + // Mock and expect IAccountingExtension.pay to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.pay, (_requestId, _requester, _proposer, _token, _amount)), + abi.encode() + ); + + vm.startPrank(address(oracle)); + httpRequestModule.finalizeRequest(_request, mockResponse, address(oracle)); + + // Mock and expect IAccountingExtension.release to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.release, (_requester, _requestId, _token, _amount)), + abi.encode(true) + ); + + httpRequestModule.finalizeRequest(_request, mockResponse, address(this)); + } + + function test_emitsEvent( + bytes32 _requestId, + address _requester, + address _proposer, + uint256 _amount, + IERC20 _token, + IOracle.Request calldata _request + ) public { + // Use the correct accounting parameters + bytes memory _requestData = abi.encode( + IHttpRequestModule.RequestParameters({ + url: URL, + method: METHOD, + body: BODY, + accountingExtension: accounting, + paymentToken: _token, + paymentAmount: _amount + }) + ); + + // Mock and expect IAccountingExtension.pay to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.pay, (_requestId, _requester, _proposer, _token, _amount)), + abi.encode() + ); + + vm.startPrank(address(oracle)); + httpRequestModule.finalizeRequest(_request, mockResponse, address(oracle)); + + // Update mock call to return the response's createdAt + _mockAndExpect(address(oracle), abi.encodeCall(IOracle.createdAt, (_getId(mockResponse))), abi.encode(0)); + + // Mock and expect IAccountingExtension.release to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.release, (_requester, _requestId, _token, _amount)), + abi.encode(true) + ); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(httpRequestModule)); + emit RequestFinalized(_requestId, address(this)); + + httpRequestModule.finalizeRequest(_request, mockResponse, address(this)); + } + + /** + * @notice Test that the finalizeRequest reverts if caller is not the oracle + */ + function test_revertsIfWrongCaller(bytes32 _requestId, address _caller, IOracle.Request calldata _request) public { + vm.assume(_caller != address(oracle)); + + // Check: does it revert if not called by the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(_caller); + httpRequestModule.finalizeRequest(_request, mockResponse, address(_caller)); + } +} From 6f98e82f1c5d551755b0730af2525d58c54908f7 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:19:54 +0400 Subject: [PATCH 3/8] feat: fix helpers --- solidity/test/utils/Helpers.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/solidity/test/utils/Helpers.sol b/solidity/test/utils/Helpers.sol index 5bddd4bc..af32e439 100644 --- a/solidity/test/utils/Helpers.sol +++ b/solidity/test/utils/Helpers.sol @@ -33,9 +33,7 @@ contract Helpers is DSTestPlus { disputer: _disputer, responseId: bytes32('response'), proposer: _proposer, - requestId: _requestId, - status: IOracle.DisputeStatus.None, - createdAt: block.timestamp + requestId: _requestId }); } From 16f8ed13458ed84064f51bee7b8a3284ba23d548 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:57:38 +0400 Subject: [PATCH 4/8] feat: update `prophet-core` package --- package.json | 2 +- yarn.lock | 84 +++++++++++++++++++++++++--------------------------- 2 files changed, 41 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index b7700bc1..dcb5ecad 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "package.json": "sort-package-json" }, "dependencies": { - "@defi-wonderland/prophet-core-contracts": "0.0.0-d05a00d0", + "@defi-wonderland/prophet-core-contracts": "0.0.0-a1d2cc55", "@defi-wonderland/solidity-utils": "0.0.0-3e9c8e8b", "@openzeppelin/contracts": "^4.9.3", "ds-test": "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0", diff --git a/yarn.lock b/yarn.lock index c61b7709..f3d82dcb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -192,10 +192,10 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@defi-wonderland/prophet-core-contracts@0.0.0-d05a00d0": - version "0.0.0-d05a00d0" - resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-core-contracts/-/prophet-core-contracts-0.0.0-d05a00d0.tgz#1357d917fe46a5a12faa67f557e990255dda14fd" - integrity sha512-F/y0r/qDLFACzsN7Y2VRAPIS9Yhx2btU/m7cQT7T84TbIxAmBGVw6/7nb+HeIbXh+QDO90RP6vHAdQOow/q1Xw== +"@defi-wonderland/prophet-core-contracts@0.0.0-a1d2cc55": + version "0.0.0-a1d2cc55" + resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-core-contracts/-/prophet-core-contracts-0.0.0-a1d2cc55.tgz#e0bba63cdb143ffba6721049d2b0577eb39329fb" + integrity sha512-gl+8QvkzPd144yESzXhl2ceJ5blZczKh7HLioSfJ1uZnrJAR90z/MJD1fCb/1Q3oDyaq6WMWnJSK9VvMkadTtQ== dependencies: "@defi-wonderland/solidity-utils" "0.0.0-3e9c8e8b" "@openzeppelin/contracts" "^4.9.3" @@ -274,9 +274,9 @@ antlr4ts "^0.5.0-alpha.4" "@solidity-parser/parser@^0.16.0": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.1.tgz#f7c8a686974e1536da0105466c4db6727311253c" - integrity sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw== + version "0.16.2" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.2.tgz#42cb1e3d88b3e8029b0c9befff00b634cd92d2fa" + integrity sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg== dependencies: antlr4ts "^0.5.0-alpha.4" @@ -324,9 +324,9 @@ ts-essentials "^7.0.1" "@types/minimist@^1.2.0": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.4.tgz#81f886786411c45bba3f33e781ab48bd56bfca2e" - integrity sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ== + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" + integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== "@types/node@20.5.1": version "20.5.1" @@ -334,9 +334,9 @@ integrity sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg== "@types/normalize-package-data@^2.4.0": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz#291c243e4b94dbfbc0c0ee26b7666f1d5c030e2c" - integrity sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg== + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== "@types/prettier@^2.1.1": version "2.7.3" @@ -357,9 +357,9 @@ acorn-jsx@^5.0.0: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + version "8.3.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f" + integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== acorn@^6.0.7: version "6.4.2" @@ -367,9 +367,9 @@ acorn@^6.0.7: integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== acorn@^8.4.1: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== add-stream@^1.0.0: version "1.0.0" @@ -1181,14 +1181,10 @@ dotgitignore@^2.1.0: find-up "^3.0.0" minimatch "^3.0.4" -"ds-test@git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0": - version "1.0.0" - resolved "git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0" - -"ds-test@https://github.com/dapphub/ds-test": +"ds-test@git+https://github.com/dapphub/ds-test.git", "ds-test@git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0": version "1.0.0" uid e282159d5170298eb2455a6c05280ab5a73a4ef0 - resolved "https://github.com/dapphub/ds-test#e282159d5170298eb2455a6c05280ab5a73a4ef0" + resolved "git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0" "ds-test@https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0": version "1.0.0" @@ -1457,9 +1453,9 @@ fast-diff@^1.1.2, fast-diff@^1.2.0: integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== fast-glob@^3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -1570,14 +1566,14 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +"forge-std@git+https://github.com/foundry-rs/forge-std.git": + version "1.7.1" + resolved "git+https://github.com/foundry-rs/forge-std.git#37a37ab73364d6644bfe11edf88a07880f99bd56" + "forge-std@git+https://github.com/foundry-rs/forge-std.git#e8a047e3f40f13fa37af6fe14e6e06283d9a060e": version "1.5.6" resolved "git+https://github.com/foundry-rs/forge-std.git#e8a047e3f40f13fa37af6fe14e6e06283d9a060e" -"forge-std@https://github.com/foundry-rs/forge-std": - version "1.7.1" - resolved "https://github.com/foundry-rs/forge-std#267acd30a625086b3f16e1a28cfe0c5097fa46b8" - "forge-std@https://github.com/foundry-rs/forge-std.git#f73c73d2018eb6a111f35e4dae7b4f27401e9421": version "1.7.1" resolved "https://github.com/foundry-rs/forge-std.git#f73c73d2018eb6a111f35e4dae7b4f27401e9421" @@ -2858,9 +2854,9 @@ progress@^2.0.0: integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== q@^1.5.1: version "1.5.1" @@ -3265,9 +3261,9 @@ solhint@3.5.1: prettier "^2.8.3" solidity-ast@^0.4.38: - version "0.4.52" - resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.52.tgz#9f1a9abc7e5ba28bbf91146ecd07aec7e70f3c85" - integrity sha512-iOya9BSiB9jhM8Vf40n8lGELGzwrUc57rl5BhfNtJ5cvAaMvRcNlHeAMNvqJJyjoUnczqRbHqdivEqK89du3Cw== + version "0.4.53" + resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.53.tgz#098259472fccd234ff00f050afaf7843a7ccd635" + integrity sha512-/7xYF//mAt4iP9S21fCFSLMouYXzXJqrd84jbI1LHL1rq7XhyFLUXxVcRkl9KqEEQmI+DmDbTeS6W5cEwcJ2wQ== dependencies: array.prototype.findlast "^1.2.2" @@ -3787,9 +3783,9 @@ universalify@^0.1.0: integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== uri-js@^4.2.2: version "4.4.1" @@ -3916,9 +3912,9 @@ yallist@^4.0.0: integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@^2.2.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.3.tgz#01f6d18ef036446340007db8e016810e5d64aad9" - integrity sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ== + version "2.3.4" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" + integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" From 3c2813359555007e1e74bf7a0cb73774952f1d51 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sat, 11 Nov 2023 14:37:26 +0400 Subject: [PATCH 5/8] feat: update helpers --- solidity/test/utils/Helpers.sol | 70 ++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/solidity/test/utils/Helpers.sol b/solidity/test/utils/Helpers.sol index af32e439..4f22491b 100644 --- a/solidity/test/utils/Helpers.sol +++ b/solidity/test/utils/Helpers.sol @@ -6,37 +6,53 @@ import {DSTestPlus} from '@defi-wonderland/solidity-utils/solidity/test/DSTestPl import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; import {IAccountingExtension} from '../../interfaces/extensions/IAccountingExtension.sol'; +import {TestConstants} from './TestConstants.sol'; + +contract Helpers is DSTestPlus, TestConstants { + // 100% random sequence of bytes representing request, response, or dispute id + bytes32 public mockId = bytes32('69'); + + // Placeholder addresses + address public disputer = makeAddr('disputer'); + address public proposer = makeAddr('proposer'); + + // Mocks objects + IOracle.Request public mockRequest; + IOracle.Response public mockResponse = IOracle.Response({proposer: proposer, requestId: mockId, response: bytes('')}); + IOracle.Dispute public mockDispute = + IOracle.Dispute({disputer: disputer, responseId: mockId, proposer: proposer, requestId: mockId}); + + // Shared events that all modules emit + event RequestFinalized(bytes32 indexed _requestId, IOracle.Response _response, address _finalizer); -contract Helpers is DSTestPlus { modifier assumeFuzzable(address _address) { _assumeFuzzable(_address); _; } + /** + * @notice Ensures that a fuzzed address can be used for deployment and calls + * + * @param _address The address to check + */ function _assumeFuzzable(address _address) internal pure { assumeNotForgeAddress(_address); assumeNotZeroAddress(_address); assumeNotPrecompile(_address); } + /** + * @notice Sets up a mock and expects a call to it + * + * @param _receiver The address to have a mock on + * @param _calldata The calldata to mock and expect + * @param _returned The data to return from the mocked call + */ function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { vm.mockCall(_receiver, _calldata, _returned); vm.expectCall(_receiver, _calldata); } - function _getMockDispute( - bytes32 _requestId, - address _disputer, - address _proposer - ) internal view returns (IOracle.Dispute memory _dispute) { - _dispute = IOracle.Dispute({ - disputer: _disputer, - responseId: bytes32('response'), - proposer: _proposer, - requestId: _requestId - }); - } - function _forBondDepositERC20( IAccountingExtension _accountingExtension, address _depositor, @@ -52,14 +68,32 @@ contract Helpers is DSTestPlus { vm.stopPrank(); } - function _getId(IOracle.Response memory _response) internal pure returns (bytes32 _id) { - _id = keccak256(abi.encode(_response)); - } - + /** + * @notice Computes the ID of a given request as it's done in the Oracle + * + * @param _request The request to compute the ID for + * @return _id The ID of the request + */ function _getId(IOracle.Request memory _request) internal pure returns (bytes32 _id) { _id = keccak256(abi.encode(_request)); } + /** + * @notice Computes the ID of a given response as it's done in the Oracle + * + * @param _response The response to compute the ID for + * @return _id The ID of the response + */ + function _getId(IOracle.Response memory _response) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_response)); + } + + /** + * @notice Computes the ID of a given dispute as it's done in the Oracle + * + * @param _dispute The dispute to compute the ID for + * @return _id The ID of the dispute + */ function _getId(IOracle.Dispute memory _dispute) internal pure returns (bytes32 _id) { _id = keccak256(abi.encode(_dispute)); } From 826470634eeec373e7d075206d4d9cbf83346d2d Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sat, 11 Nov 2023 15:28:57 +0400 Subject: [PATCH 6/8] test: fix unit tests --- .../modules/request/HttpRequestModule.t.sol | 122 ++++++++++-------- 1 file changed, 68 insertions(+), 54 deletions(-) diff --git a/solidity/test/unit/modules/request/HttpRequestModule.t.sol b/solidity/test/unit/modules/request/HttpRequestModule.t.sol index e7e300e7..a7baf406 100644 --- a/solidity/test/unit/modules/request/HttpRequestModule.t.sol +++ b/solidity/test/unit/modules/request/HttpRequestModule.t.sol @@ -18,23 +18,15 @@ import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountin */ contract BaseTest is Test, Helpers { // Mock request data - string public constant URL = 'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd'; + // Fuzzing enums doesn't work: https://github.com/foundry-rs/foundry/issues/871 IHttpRequestModule.HttpMethod public constant METHOD = IHttpRequestModule.HttpMethod.GET; - string public constant BODY = '69420'; - // Mock token - IERC20 public immutable TOKEN = IERC20(makeAddr('ERC20')); // The target contract HttpRequestModule public httpRequestModule; // A mock oracle IOracle public oracle; // A mock accounting extension - IAccountingExtension public accounting = IAccountingExtension(makeAddr('accounting')); - IOracle.Response public mockResponse; - address internal _proposer = makeAddr('proposer'); - bytes32 public mockId = bytes32('69'); - - event RequestFinalized(bytes32 indexed _requestId, address _finalizer); + IAccountingExtension public accounting; /** * @notice Deploy the target and mock oracle+accounting extension @@ -47,7 +39,6 @@ contract BaseTest is Test, Helpers { vm.etch(address(accounting), hex'069420'); httpRequestModule = new HttpRequestModule(oracle); - mockResponse = IOracle.Response({proposer: _proposer, requestId: mockId, response: bytes('')}); } } @@ -62,12 +53,12 @@ contract HttpRequestModule_Unit_ModuleData is BaseTest { /** * @notice Test that the decodeRequestData function returns the correct values */ - function test_decodeRequestData(bytes32 _requestId, uint256 _amount, IERC20 _token) public { + function test_decodeRequestData(string memory _url, string memory _body, uint256 _amount, IERC20 _token) public { bytes memory _requestData = abi.encode( IHttpRequestModule.RequestParameters({ - url: URL, + url: _url, + body: _body, method: METHOD, - body: BODY, accountingExtension: accounting, paymentToken: _token, paymentAmount: _amount @@ -78,9 +69,9 @@ contract HttpRequestModule_Unit_ModuleData is BaseTest { IHttpRequestModule.RequestParameters memory _params = httpRequestModule.decodeRequestData(_requestData); // Check: decoded values match original values? - assertEq(_params.url, URL); + assertEq(_params.url, _url); assertEq(uint256(_params.method), uint256(METHOD)); - assertEq(_params.body, BODY); + assertEq(_params.body, _body); assertEq(address(_params.accountingExtension), address(accounting)); assertEq(address(_params.paymentToken), address(_token)); assertEq(_params.paymentAmount, _amount); @@ -95,77 +86,99 @@ contract HttpRequestModule_Unit_FinalizeRequest is BaseTest { * - accounting extension pay * - accounting extension release */ - function test_makesCalls( - bytes32 _requestId, - address _requester, - address _proposer, + function test_finalizeWithResponse( + string calldata _url, + string calldata _body, uint256 _amount, - IERC20 _token, - IOracle.Request calldata _request + IERC20 _token ) public { _amount = bound(_amount, 0, type(uint248).max); // Use the correct accounting parameters - bytes memory _requestData = abi.encode( + mockRequest.requestModuleData = abi.encode( IHttpRequestModule.RequestParameters({ - url: URL, + url: _url, method: METHOD, - body: BODY, + body: _body, accountingExtension: accounting, paymentToken: _token, paymentAmount: _amount }) ); - // Mock and expect IAccountingExtension.pay to be called + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + + // Mock and expect oracle to return the response's creation time _mockAndExpect( - address(accounting), - abi.encodeCall(IAccountingExtension.pay, (_requestId, _requester, _proposer, _token, _amount)), - abi.encode() + address(oracle), abi.encodeCall(IOracle.createdAt, (_getId(mockResponse))), abi.encode(block.timestamp) ); - vm.startPrank(address(oracle)); - httpRequestModule.finalizeRequest(_request, mockResponse, address(oracle)); - - // Mock and expect IAccountingExtension.release to be called + // Mock and expect IAccountingExtension.pay to be called _mockAndExpect( address(accounting), - abi.encodeCall(IAccountingExtension.release, (_requester, _requestId, _token, _amount)), - abi.encode(true) + abi.encodeCall( + IAccountingExtension.pay, (_requestId, mockRequest.requester, mockResponse.proposer, _token, _amount) + ), + abi.encode() ); - httpRequestModule.finalizeRequest(_request, mockResponse, address(this)); + vm.startPrank(address(oracle)); + httpRequestModule.finalizeRequest(mockRequest, mockResponse, address(this)); } - function test_emitsEvent( - bytes32 _requestId, - address _requester, - address _proposer, + function test_finalizeWithoutResponse( + string calldata _url, + string calldata _body, uint256 _amount, - IERC20 _token, - IOracle.Request calldata _request + IERC20 _token ) public { + _amount = bound(_amount, 0, type(uint248).max); + // Use the correct accounting parameters - bytes memory _requestData = abi.encode( + mockRequest.requestModuleData = abi.encode( IHttpRequestModule.RequestParameters({ - url: URL, + url: _url, method: METHOD, - body: BODY, + body: _body, accountingExtension: accounting, paymentToken: _token, paymentAmount: _amount }) ); - // Mock and expect IAccountingExtension.pay to be called + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + + // Mock and expect oracle to return no timestamp + _mockAndExpect(address(oracle), abi.encodeCall(IOracle.createdAt, (_getId(mockResponse))), abi.encode(0)); + + // Mock and expect IAccountingExtension.release to be called _mockAndExpect( address(accounting), - abi.encodeCall(IAccountingExtension.pay, (_requestId, _requester, _proposer, _token, _amount)), - abi.encode() + abi.encodeCall(IAccountingExtension.release, (mockRequest.requester, _requestId, _token, _amount)), + abi.encode(true) ); vm.startPrank(address(oracle)); - httpRequestModule.finalizeRequest(_request, mockResponse, address(oracle)); + httpRequestModule.finalizeRequest(mockRequest, mockResponse, address(this)); + } + + function test_emitsEvent(string calldata _url, string calldata _body, uint256 _amount, IERC20 _token) public { + // Use the correct accounting parameters + mockRequest.requestModuleData = abi.encode( + IHttpRequestModule.RequestParameters({ + url: _url, + method: METHOD, + body: _body, + accountingExtension: accounting, + paymentToken: _token, + paymentAmount: _amount + }) + ); + + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; // Update mock call to return the response's createdAt _mockAndExpect(address(oracle), abi.encodeCall(IOracle.createdAt, (_getId(mockResponse))), abi.encode(0)); @@ -173,27 +186,28 @@ contract HttpRequestModule_Unit_FinalizeRequest is BaseTest { // Mock and expect IAccountingExtension.release to be called _mockAndExpect( address(accounting), - abi.encodeCall(IAccountingExtension.release, (_requester, _requestId, _token, _amount)), + abi.encodeCall(IAccountingExtension.release, (mockRequest.requester, _requestId, _token, _amount)), abi.encode(true) ); // Check: is the event emitted? vm.expectEmit(true, true, true, true, address(httpRequestModule)); - emit RequestFinalized(_requestId, address(this)); + emit RequestFinalized(_requestId, mockResponse, address(this)); - httpRequestModule.finalizeRequest(_request, mockResponse, address(this)); + vm.prank(address(oracle)); + httpRequestModule.finalizeRequest(mockRequest, mockResponse, address(this)); } /** * @notice Test that the finalizeRequest reverts if caller is not the oracle */ - function test_revertsIfWrongCaller(bytes32 _requestId, address _caller, IOracle.Request calldata _request) public { + function test_revertsIfWrongCaller(address _caller) public { vm.assume(_caller != address(oracle)); // Check: does it revert if not called by the Oracle? vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); vm.prank(_caller); - httpRequestModule.finalizeRequest(_request, mockResponse, address(_caller)); + httpRequestModule.finalizeRequest(mockRequest, mockResponse, address(_caller)); } } From ae2e40a7bbe3d5f2c385145ad0060e3c99842078 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sat, 11 Nov 2023 15:30:44 +0400 Subject: [PATCH 7/8] docs: clarify docs and natspec --- .../modules/request/http_request_module.md | 2 +- .../modules/request/IHttpRequestModule.sol | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/src/content/modules/request/http_request_module.md b/docs/src/content/modules/request/http_request_module.md index 332e16d5..faf5de94 100644 --- a/docs/src/content/modules/request/http_request_module.md +++ b/docs/src/content/modules/request/http_request_module.md @@ -10,7 +10,7 @@ The `HttpRequestModule` is a contract that allows users to request HTTP calls. ### Key Methods -- `decodeRequestData(bytes calldata _data)`: This method decodes the data for a request given its ID. It returns the URL, HTTP method, body, accounting extension, payment token, and payment amount associated with the request. +- `decodeRequestData`: This method decodes request parameters. It returns the URL, HTTP method, body, accounting extension, payment token, and payment amount from the given data. - `finalizeRequest`: This method finalizes a request by paying the proposer if there is a valid response, or releases the requester bond if no valid response was provided. ### Request Parameters diff --git a/solidity/interfaces/modules/request/IHttpRequestModule.sol b/solidity/interfaces/modules/request/IHttpRequestModule.sol index 9a01a797..a4e3db93 100644 --- a/solidity/interfaces/modules/request/IHttpRequestModule.sol +++ b/solidity/interfaces/modules/request/IHttpRequestModule.sol @@ -52,20 +52,27 @@ interface IHttpRequestModule is IRequestModule { /** * @notice Returns the decoded data for a request - * @param _data The encoded request parameters - * @return _params The struct containing the parameters for the request + * + * @param _data The encoded request parameters + * @return _params The struct containing the parameters for the request */ function decodeRequestData(bytes calldata _data) external view returns (RequestParameters memory _params); - /// @inheritdoc IRequestModule + /** + * @notice Executes pre-request logic, bonding the requester's funds + * + * @param _requestId The id of the request + * @param _data The encoded request parameters + * @param _requester The user who triggered the request + */ function createRequest(bytes32 _requestId, bytes calldata _data, address _requester) external; /** * @notice Finalizes the request by paying the proposer for the response or releasing the requester's bond if no response was submitted * - * @param _request The request that is being finalized - * @param _response The final response - * @param _finalizer The user who triggered the finalization + * @param _request The request that is being finalized + * @param _response The final response + * @param _finalizer The user who triggered the finalization */ function finalizeRequest( IOracle.Request calldata _request, From 008420483bf123254879822d0ce6bb09e654bac9 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sat, 11 Nov 2023 15:30:51 +0400 Subject: [PATCH 8/8] fix: onlyOracle modifier --- solidity/contracts/modules/request/HttpRequestModule.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solidity/contracts/modules/request/HttpRequestModule.sol b/solidity/contracts/modules/request/HttpRequestModule.sol index ad5324fc..78531a18 100644 --- a/solidity/contracts/modules/request/HttpRequestModule.sol +++ b/solidity/contracts/modules/request/HttpRequestModule.sol @@ -21,7 +21,7 @@ contract HttpRequestModule is Module, IHttpRequestModule { } /// @inheritdoc IHttpRequestModule - function createRequest(bytes32 _requestId, bytes calldata _data, address _requester) external { + function createRequest(bytes32 _requestId, bytes calldata _data, address _requester) external onlyOracle { RequestParameters memory _params = decodeRequestData(_data); _params.accountingExtension.bond({