From b9f72cb08db77a2b59df794e0af57709aecca218 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:20:54 +0400 Subject: [PATCH 1/9] perf: optimize `ContractCallRequestModule` --- .../request/contract_call_request_module.md | 4 +- .../request/ContractCallRequestModule.sol | 112 ++-- .../request/IContractCallRequestModule.sol | 91 ++-- .../request/ContractCallRequestModule.t.sol | 513 +++++++----------- 4 files changed, 311 insertions(+), 409 deletions(-) diff --git a/docs/src/content/modules/request/contract_call_request_module.md b/docs/src/content/modules/request/contract_call_request_module.md index afbf631a..84e3f616 100644 --- a/docs/src/content/modules/request/contract_call_request_module.md +++ b/docs/src/content/modules/request/contract_call_request_module.md @@ -10,8 +10,8 @@ The `ContractCallRequestModule` is a module for requesting on-chain information. ### Key Methods -- `decodeRequestData(bytes32 _requestId)`: This method decodes the request data for a given request ID. It returns the target contract address, the function selector, the encoded arguments of the function to call, the accounting extension to bond and release funds, the payment token, and the payment amount. -- `finalizeRequest(bytes32 _requestId, address)`: This method finalizes a request by paying the response proposer. It is only callable by the oracle. +- `decodeRequestData(bytes calldata _data)`: This method decodes the request data for a given request ID. It returns the target contract address, the function selector, the encoded arguments of the function to call, the accounting extension to bond and release funds, the payment token, and the payment amount. +- `finalizeRequest`: This method finalizes a request by paying the response proposer. It is only callable by the oracle. ### Request Parameters diff --git a/solidity/contracts/modules/request/ContractCallRequestModule.sol b/solidity/contracts/modules/request/ContractCallRequestModule.sol index afd6b55b..578e552a 100644 --- a/solidity/contracts/modules/request/ContractCallRequestModule.sol +++ b/solidity/contracts/modules/request/ContractCallRequestModule.sol @@ -1,64 +1,62 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; +// 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'; +// 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 {IContractCallRequestModule} from '../../../interfaces/modules/request/IContractCallRequestModule.sol'; +import {IContractCallRequestModule} from '../../../interfaces/modules/request/IContractCallRequestModule.sol'; -// contract ContractCallRequestModule is Module, IContractCallRequestModule { -// constructor(IOracle _oracle) Module(_oracle) {} +contract ContractCallRequestModule is Module, IContractCallRequestModule { + constructor(IOracle _oracle) Module(_oracle) {} -// /// @inheritdoc IModule -// function moduleName() public pure returns (string memory _moduleName) { -// _moduleName = 'ContractCallRequestModule'; -// } + /// @inheritdoc IModule + function moduleName() public pure returns (string memory _moduleName) { + _moduleName = 'ContractCallRequestModule'; + } -// /// @inheritdoc IContractCallRequestModule -// function decodeRequestData(bytes32 _requestId) public view returns (RequestParameters memory _params) { -// _params = abi.decode(requestData[_requestId], (RequestParameters)); -// } + /// @inheritdoc IContractCallRequestModule + function decodeRequestData(bytes calldata _data) public pure returns (RequestParameters memory _params) { + _params = abi.decode(_data, (RequestParameters)); + } -// /** -// * @notice Bonds the requester's funds through the accounting extension -// * @param _requestId The id of the request being set up -// */ -// 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 IContractCallRequestModule + function createRequest(bytes32 _requestId, bytes calldata _data, address _requester) external { + RequestParameters memory _params = decodeRequestData(_data); -// /// @inheritdoc IContractCallRequestModule -// function finalizeRequest( -// bytes32 _requestId, -// address _finalizer -// ) external override(IContractCallRequestModule, 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); -// } -// } + _params.accountingExtension.bond({ + _bonder: _requester, + _requestId: _requestId, + _token: _params.paymentToken, + _amount: _params.paymentAmount + }); + } + + /// @inheritdoc IContractCallRequestModule + function finalizeRequest( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + address _finalizer + ) external override(IContractCallRequestModule, 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/IContractCallRequestModule.sol b/solidity/interfaces/modules/request/IContractCallRequestModule.sol index 1d533d6b..c68dbff1 100644 --- a/solidity/interfaces/modules/request/IContractCallRequestModule.sol +++ b/solidity/interfaces/modules/request/IContractCallRequestModule.sol @@ -1,45 +1,56 @@ -// // 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 {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'; +import {IAccountingExtension} from '../../../interfaces/extensions/IAccountingExtension.sol'; -// /** -// * @title ContractCallRequestModule -// * @notice Request module for making contract calls -// */ -// interface IContractCallRequestModule is IRequestModule { -// /** -// * @notice Parameters of the request as stored in the module -// * @param target The address of the contract to do the call on -// * @param functionSelector The selector of the function to call -// * @param data The encoded arguments of the function to call (optional) -// * @param accountingExtension The accounting extension to bond and release funds -// * @param paymentToken The token in which the response proposer will be paid -// * @param paymentAmount The amount of `paymentToken` to pay to the response proposer -// */ -// struct RequestParameters { -// address target; -// bytes4 functionSelector; -// bytes data; -// IAccountingExtension accountingExtension; -// IERC20 paymentToken; -// uint256 paymentAmount; -// } +/** + * @title ContractCallRequestModule + * @notice Request module for making contract calls + */ +interface IContractCallRequestModule is IRequestModule { + /** + * @notice Parameters of the request as stored in the module + * @param target The address of the contract to do the call on + * @param functionSelector The selector of the function to call + * @param data The encoded arguments of the function to call (optional) + * @param accountingExtension The accounting extension to bond and release funds + * @param paymentToken The token in which the response proposer will be paid + * @param paymentAmount The amount of `paymentToken` to pay to the response proposer + */ + struct RequestParameters { + address target; + bytes4 functionSelector; + bytes data; + IAccountingExtension accountingExtension; + IERC20 paymentToken; + uint256 paymentAmount; + } -// /** -// * @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 response proposer -// * @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/ContractCallRequestModule.t.sol b/solidity/test/unit/modules/request/ContractCallRequestModule.t.sol index 0fcf779a..5e15640d 100644 --- a/solidity/test/unit/modules/request/ContractCallRequestModule.t.sol +++ b/solidity/test/unit/modules/request/ContractCallRequestModule.t.sol @@ -1,310 +1,203 @@ -// // 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 { -// ContractCallRequestModule, -// IContractCallRequestModule -// } from '../../../../contracts/modules/request/ContractCallRequestModule.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_ContractCallRequestModule is ContractCallRequestModule { -// constructor(IOracle _oracle) ContractCallRequestModule(_oracle) {} - -// function forTest_setRequestData(bytes32 _requestId, bytes memory _data) public { -// requestData[_requestId] = _data; -// } -// } - -// /** -// * @title HTTP Request Module Unit tests -// */ -// contract BaseTest is Test, Helpers { -// // The target contract -// ForTest_ContractCallRequestModule public contractCallRequestModule; -// // A mock oracle -// IOracle public oracle; -// // A mock accounting extension -// IAccountingExtension public accounting; -// // A mock user for testing -// address internal _user = makeAddr('user'); -// // A second mock user for testing -// address internal _user2 = makeAddr('user2'); -// // A mock ERC20 token -// IERC20 internal _token = IERC20(makeAddr('ERC20')); -// // Mock data -// address internal _targetContract = address(_token); -// bytes4 internal _functionSelector = bytes4(abi.encodeWithSignature('allowance(address,address)')); -// bytes internal _dataParams = abi.encode(_user, _user2); - -// 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'); - -// contractCallRequestModule = new ForTest_ContractCallRequestModule(oracle); -// } -// } - -// contract ContractCallRequestModule_Unit_ModuleData is BaseTest { -// /** -// * @notice Test that the moduleName function returns the correct name -// */ -// function test_moduleNameReturnsName() public { -// assertEq(contractCallRequestModule.moduleName(), 'ContractCallRequestModule', 'Wrong module name'); -// } - -// /** -// * @notice Test that the decodeRequestData function returns the correct values -// */ -// function test_decodeRequestData(bytes32 _requestId, IERC20 _paymentToken, uint256 _paymentAmount) public { -// bytes memory _requestData = abi.encode( -// IContractCallRequestModule.RequestParameters({ -// target: _targetContract, -// functionSelector: _functionSelector, -// data: _dataParams, -// accountingExtension: accounting, -// paymentToken: _paymentToken, -// paymentAmount: _paymentAmount -// }) -// ); - -// // Set the request data -// contractCallRequestModule.forTest_setRequestData(_requestId, _requestData); - -// // Decode the given request data -// IContractCallRequestModule.RequestParameters memory _params = -// contractCallRequestModule.decodeRequestData(_requestId); - -// // Check: decoded values match original values? -// assertEq(_params.target, _targetContract, 'Mismatch: decoded target'); -// assertEq(_params.functionSelector, _functionSelector, 'Mismatch: decoded function selector'); -// assertEq(_params.data, _dataParams, 'Mismatch: decoded data'); -// assertEq(address(_params.accountingExtension), address(accounting), 'Mismatch: decoded accounting extension'); -// assertEq(address(_params.paymentToken), address(_paymentToken), 'Mismatch: decoded payment token'); -// assertEq(_params.paymentAmount, _paymentAmount, 'Mismatch: decoded payment amount'); -// } -// } - -// contract ContractCallRequestModule_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, -// IERC20 _paymentToken, -// uint256 _paymentAmount -// ) public { -// bytes memory _requestData = abi.encode( -// IContractCallRequestModule.RequestParameters({ -// target: _targetContract, -// functionSelector: _functionSelector, -// data: _dataParams, -// accountingExtension: accounting, -// paymentToken: _paymentToken, -// paymentAmount: _paymentAmount -// }) -// ); - -// 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, _paymentToken, _paymentAmount -// ), -// abi.encode(true) -// ); - -// vm.prank(address(oracle)); -// contractCallRequestModule.setupRequest(_requestId, _requestData); - -// // Check: request data was set? -// assertEq(contractCallRequestModule.requestData(_requestId), _requestData, 'Mismatch: Request data'); -// } -// } - -// contract ContractCallRequestModule_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, -// IERC20 _paymentToken, -// uint256 _paymentAmount -// ) public { -// // Use the correct accounting parameters -// bytes memory _requestData = abi.encode( -// IContractCallRequestModule.RequestParameters({ -// target: _targetContract, -// functionSelector: _functionSelector, -// data: _dataParams, -// accountingExtension: accounting, -// paymentToken: _paymentToken, -// paymentAmount: _paymentAmount -// }) -// ); - -// IOracle.Request memory _fullRequest; -// _fullRequest.requester = _requester; - -// IOracle.Response memory _fullResponse; -// _fullResponse.proposer = _proposer; -// _fullResponse.createdAt = block.timestamp; - -// // Set the request data -// contractCallRequestModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock and expect IOracle.getRequest to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getRequest, (_requestId)), abi.encode(_fullRequest)); -// vm.expectCall(address(oracle), abi.encodeCall(IOracle.getRequest, (_requestId))); - -// // 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, _paymentToken, _paymentAmount)), -// abi.encode() -// ); - -// vm.startPrank(address(oracle)); -// contractCallRequestModule.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, _paymentToken, _paymentAmount)), -// abi.encode(true) -// ); - -// contractCallRequestModule.finalizeRequest(_requestId, address(this)); -// } - -// function test_emitsEvent( -// bytes32 _requestId, -// address _requester, -// address _proposer, -// IERC20 _paymentToken, -// uint256 _paymentAmount -// ) public { -// // Use the correct accounting parameters -// bytes memory _requestData = abi.encode( -// IContractCallRequestModule.RequestParameters({ -// target: _targetContract, -// functionSelector: _functionSelector, -// data: _dataParams, -// accountingExtension: accounting, -// paymentToken: _paymentToken, -// paymentAmount: _paymentAmount -// }) -// ); - -// IOracle.Request memory _fullRequest; -// _fullRequest.requester = _requester; - -// IOracle.Response memory _fullResponse; -// _fullResponse.proposer = _proposer; -// _fullResponse.createdAt = block.timestamp; - -// // Set the request data -// contractCallRequestModule.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 IAccountingExtension.pay to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.pay, (_requestId, _requester, _proposer, _paymentToken, _paymentAmount)), -// abi.encode() -// ); - -// // Mock and expect IOracle.getFinalizedResponse to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, (_requestId)), abi.encode(_fullResponse) -// ); - -// vm.startPrank(address(oracle)); -// contractCallRequestModule.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, _paymentToken, _paymentAmount)), -// abi.encode(true) -// ); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(contractCallRequestModule)); -// emit RequestFinalized(_requestId, address(this)); - -// contractCallRequestModule.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); -// contractCallRequestModule.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 { + ContractCallRequestModule, + IContractCallRequestModule +} from '../../../../contracts/modules/request/ContractCallRequestModule.sol'; + +import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountingExtension.sol'; + +/** + * @title Contract Call Request Module Unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + ContractCallRequestModule public contractCallRequestModule; + // A mock oracle + IOracle public oracle; + // A mock accounting extension + IAccountingExtension public accounting; + // A mock user for testing + address internal _user = makeAddr('user'); + // A second mock user for testing + address internal _user2 = makeAddr('user2'); + // A mock ERC20 token + IERC20 internal _token = IERC20(makeAddr('ERC20')); + // Mock data + address internal _targetContract = address(_token); + bytes4 internal _functionSelector = bytes4(abi.encodeWithSignature('allowance(address,address)')); + bytes internal _dataParams = abi.encode(_user, _user2); + bytes32 public mockId = bytes32('69'); + address internal _proposer = makeAddr('proposer'); + // Create a new dummy response + IOracle.Response public mockResponse; + + 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'); + + contractCallRequestModule = new ContractCallRequestModule(oracle); + + mockResponse = IOracle.Response({proposer: _proposer, requestId: mockId, response: bytes('')}); + } +} + +contract ContractCallRequestModule_Unit_ModuleData is BaseTest { + /** + * @notice Test that the moduleName function returns the correct name + */ + function test_moduleNameReturnsName() public { + assertEq(contractCallRequestModule.moduleName(), 'ContractCallRequestModule', 'Wrong module name'); + } + + /** + * @notice Test that the decodeRequestData function returns the correct values + */ + function test_decodeRequestData(bytes32 _requestId, IERC20 _paymentToken, uint256 _paymentAmount) public { + bytes memory _requestData = abi.encode( + IContractCallRequestModule.RequestParameters({ + target: _targetContract, + functionSelector: _functionSelector, + data: _dataParams, + accountingExtension: accounting, + paymentToken: _paymentToken, + paymentAmount: _paymentAmount + }) + ); + + // Decode the given request data + IContractCallRequestModule.RequestParameters memory _params = + contractCallRequestModule.decodeRequestData(_requestData); + + // Check: decoded values match original values? + assertEq(_params.target, _targetContract, 'Mismatch: decoded target'); + assertEq(_params.functionSelector, _functionSelector, 'Mismatch: decoded function selector'); + assertEq(_params.data, _dataParams, 'Mismatch: decoded data'); + assertEq(address(_params.accountingExtension), address(accounting), 'Mismatch: decoded accounting extension'); + assertEq(address(_params.paymentToken), address(_paymentToken), 'Mismatch: decoded payment token'); + assertEq(_params.paymentAmount, _paymentAmount, 'Mismatch: decoded payment amount'); + } +} + +contract ContractCallRequestModule_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, + IERC20 _paymentToken, + uint256 _paymentAmount, + IOracle.Request calldata _request + ) public { + // Use the correct accounting parameters + bytes memory _requestData = abi.encode( + IContractCallRequestModule.RequestParameters({ + target: _targetContract, + functionSelector: _functionSelector, + data: _dataParams, + accountingExtension: accounting, + paymentToken: _paymentToken, + paymentAmount: _paymentAmount + }) + ); + + // Mock and expect IAccountingExtension.pay to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.pay, (_requestId, _requester, _proposer, _paymentToken, _paymentAmount)), + abi.encode() + ); + + vm.startPrank(address(oracle)); + contractCallRequestModule.finalizeRequest(_request, mockResponse, address(oracle)); + + // Mock and expect IAccountingExtension.release to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.release, (_requester, _requestId, _paymentToken, _paymentAmount)), + abi.encode(true) + ); + + contractCallRequestModule.finalizeRequest(_request, mockResponse, address(this)); + } + + function test_emitsEvent( + bytes32 _requestId, + address _requester, + address _proposer, + IERC20 _paymentToken, + uint256 _paymentAmount, + IOracle.Request calldata _request + ) public { + // Use the correct accounting parameters + bytes memory _requestData = abi.encode( + IContractCallRequestModule.RequestParameters({ + target: _targetContract, + functionSelector: _functionSelector, + data: _dataParams, + accountingExtension: accounting, + paymentToken: _paymentToken, + paymentAmount: _paymentAmount + }) + ); + + // Mock and expect IAccountingExtension.pay to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.pay, (_requestId, _requester, _proposer, _paymentToken, _paymentAmount)), + abi.encode() + ); + + vm.startPrank(address(oracle)); + contractCallRequestModule.finalizeRequest(_request, mockResponse, address(oracle)); + + // Mock and expect IAccountingExtension.release to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.release, (_requester, _requestId, _paymentToken, _paymentAmount)), + abi.encode(true) + ); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(contractCallRequestModule)); + emit RequestFinalized(_requestId, address(this)); + + contractCallRequestModule.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); + contractCallRequestModule.finalizeRequest(_request, mockResponse, address(_caller)); + } +} From 196cda12366273bc002f272d4dd5569486312dac Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:21:55 +0400 Subject: [PATCH 2/9] feat: uncomment helpers --- .../extensions/IAccountingExtension.sol | 380 +++++++++--------- solidity/test/utils/Helpers.sol | 122 +++--- 2 files changed, 256 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..af32e439 100644 --- a/solidity/test/utils/Helpers.sol +++ b/solidity/test/utils/Helpers.sol @@ -1,56 +1,66 @@ -// // 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 + }); + } + + 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 5cf76caeacc4a44bd7a1d4ec0b81e320482f6f36 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:03:53 +0400 Subject: [PATCH 3/9] docs: remove an obsolete function --- .../content/modules/dispute/root_verification_module.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/src/content/modules/dispute/root_verification_module.md b/docs/src/content/modules/dispute/root_verification_module.md index 6bb6e1f0..b2ad1935 100644 --- a/docs/src/content/modules/dispute/root_verification_module.md +++ b/docs/src/content/modules/dispute/root_verification_module.md @@ -10,10 +10,9 @@ The Root Verification Module is a pre-dispute module that allows disputers to ca ### Key Methods -- `decodeRequestData(bytes32 _requestId)`: Returns the decoded data for a request. -- `disputeResponse(bytes32 _requestId, bytes32 _responseId, address _disputer, address _proposer)`: Calculates the correct root and compares it to the proposed one. Updates the dispute status after checking if the disputed response is indeed wrong. -- `onDisputeStatusChange(bytes32 _requestId, IOracle.Dispute memory _dispute)`: Updates the status of the dispute and resolves it by proposing the correct root as a response and finalizing the request. -- `disputeEscalated(bytes32 _disputeId)`: This function is present to comply with the module interface but it is not implemented since this is a pre-dispute module. +- `decodeRequestData`: Returns the decoded data for a request. +- `disputeResponse`: Calculates the correct root and compares it to the proposed one. Updates the dispute status after checking if the disputed response is indeed wrong. +- `onDisputeStatusChange`: Updates the status of the dispute and resolves it by proposing the correct root as a response and finalizing the request. ### Request Parameters From 76613788cf16ff03697d08338771ef8e1db6f0c4 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:04:48 +0400 Subject: [PATCH 4/9] Revert "docs: remove an obsolete function" This reverts commit 5cf76caeacc4a44bd7a1d4ec0b81e320482f6f36. --- .../content/modules/dispute/root_verification_module.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/src/content/modules/dispute/root_verification_module.md b/docs/src/content/modules/dispute/root_verification_module.md index b2ad1935..6bb6e1f0 100644 --- a/docs/src/content/modules/dispute/root_verification_module.md +++ b/docs/src/content/modules/dispute/root_verification_module.md @@ -10,9 +10,10 @@ The Root Verification Module is a pre-dispute module that allows disputers to ca ### Key Methods -- `decodeRequestData`: Returns the decoded data for a request. -- `disputeResponse`: Calculates the correct root and compares it to the proposed one. Updates the dispute status after checking if the disputed response is indeed wrong. -- `onDisputeStatusChange`: Updates the status of the dispute and resolves it by proposing the correct root as a response and finalizing the request. +- `decodeRequestData(bytes32 _requestId)`: Returns the decoded data for a request. +- `disputeResponse(bytes32 _requestId, bytes32 _responseId, address _disputer, address _proposer)`: Calculates the correct root and compares it to the proposed one. Updates the dispute status after checking if the disputed response is indeed wrong. +- `onDisputeStatusChange(bytes32 _requestId, IOracle.Dispute memory _dispute)`: Updates the status of the dispute and resolves it by proposing the correct root as a response and finalizing the request. +- `disputeEscalated(bytes32 _disputeId)`: This function is present to comply with the module interface but it is not implemented since this is a pre-dispute module. ### Request Parameters From 6e4d470fc3f41173de3b89b8f9251f2683be9e60 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 5/9] feat: update `prophet-core` package --- package.json | 2 +- yarn.lock | 78 +++++++++++++++++++++++++--------------------------- 2 files changed, 38 insertions(+), 42 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..658b1212 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" @@ -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 1cdc837cac916da10426ed66b2dacdd6856afc8e Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Fri, 10 Nov 2023 19:09:33 +0400 Subject: [PATCH 6/9] feat: tidy up 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 2190a64a8c3a9ac1cb72367b2b53415c65346b47 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sat, 11 Nov 2023 14:34:04 +0400 Subject: [PATCH 7/9] docs: natspec --- .../request/contract_call_request_module.md | 2 +- .../request/IContractCallRequestModule.sol | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/src/content/modules/request/contract_call_request_module.md b/docs/src/content/modules/request/contract_call_request_module.md index 84e3f616..5ae50f9a 100644 --- a/docs/src/content/modules/request/contract_call_request_module.md +++ b/docs/src/content/modules/request/contract_call_request_module.md @@ -10,7 +10,7 @@ The `ContractCallRequestModule` is a module for requesting on-chain information. ### Key Methods -- `decodeRequestData(bytes calldata _data)`: This method decodes the request data for a given request ID. It returns the target contract address, the function selector, the encoded arguments of the function to call, the accounting extension to bond and release funds, the payment token, and the payment amount. +- `decodeRequestData`: This method decodes the request data for a given request ID. It returns the target contract address, the function selector, the encoded arguments of the function to call, the accounting extension to bond and release funds, the payment token, and the payment amount. - `finalizeRequest`: This method finalizes a request by paying the response proposer. It is only callable by the oracle. ### Request Parameters diff --git a/solidity/interfaces/modules/request/IContractCallRequestModule.sol b/solidity/interfaces/modules/request/IContractCallRequestModule.sol index c68dbff1..e680fbeb 100644 --- a/solidity/interfaces/modules/request/IContractCallRequestModule.sol +++ b/solidity/interfaces/modules/request/IContractCallRequestModule.sol @@ -33,20 +33,27 @@ interface IContractCallRequestModule 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 204ec3b3aeafc8337520a081cdf8ef31fbb10b02 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sat, 11 Nov 2023 14:34:27 +0400 Subject: [PATCH 8/9] test: fix `ContractCallRequestModule` unit tests --- .../request/ContractCallRequestModule.t.sol | 93 ++++++++++--------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/solidity/test/unit/modules/request/ContractCallRequestModule.t.sol b/solidity/test/unit/modules/request/ContractCallRequestModule.t.sol index 5e15640d..fa3e3ebe 100644 --- a/solidity/test/unit/modules/request/ContractCallRequestModule.t.sol +++ b/solidity/test/unit/modules/request/ContractCallRequestModule.t.sol @@ -36,12 +36,6 @@ contract BaseTest is Test, Helpers { address internal _targetContract = address(_token); bytes4 internal _functionSelector = bytes4(abi.encodeWithSignature('allowance(address,address)')); bytes internal _dataParams = abi.encode(_user, _user2); - bytes32 public mockId = bytes32('69'); - address internal _proposer = makeAddr('proposer'); - // Create a new dummy response - IOracle.Response public mockResponse; - - event RequestFinalized(bytes32 indexed _requestId, address _finalizer); /** * @notice Deploy the target and mock oracle+accounting extension @@ -54,8 +48,6 @@ contract BaseTest is Test, Helpers { vm.etch(address(accounting), hex'069420'); contractCallRequestModule = new ContractCallRequestModule(oracle); - - mockResponse = IOracle.Response({proposer: _proposer, requestId: mockId, response: bytes('')}); } } @@ -70,7 +62,7 @@ contract ContractCallRequestModule_Unit_ModuleData is BaseTest { /** * @notice Test that the decodeRequestData function returns the correct values */ - function test_decodeRequestData(bytes32 _requestId, IERC20 _paymentToken, uint256 _paymentAmount) public { + function test_decodeRequestData(IERC20 _paymentToken, uint256 _paymentAmount) public { bytes memory _requestData = abi.encode( IContractCallRequestModule.RequestParameters({ target: _targetContract, @@ -104,16 +96,8 @@ contract ContractCallRequestModule_Unit_FinalizeRequest is BaseTest { * - accounting extension pay * - accounting extension release */ - function test_makesCalls( - bytes32 _requestId, - address _requester, - address _proposer, - IERC20 _paymentToken, - uint256 _paymentAmount, - IOracle.Request calldata _request - ) public { - // Use the correct accounting parameters - bytes memory _requestData = abi.encode( + function test_finalizeWithResponse(IERC20 _paymentToken, uint256 _paymentAmount) public { + mockRequest.requestModuleData = abi.encode( IContractCallRequestModule.RequestParameters({ target: _targetContract, functionSelector: _functionSelector, @@ -124,36 +108,60 @@ contract ContractCallRequestModule_Unit_FinalizeRequest is BaseTest { }) ); + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + + // Mock and expect oracle to return the response's creation time + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.createdAt, (_getId(mockResponse))), abi.encode(block.timestamp) + ); + // Mock and expect IAccountingExtension.pay to be called _mockAndExpect( address(accounting), - abi.encodeCall(IAccountingExtension.pay, (_requestId, _requester, _proposer, _paymentToken, _paymentAmount)), + abi.encodeCall( + IAccountingExtension.pay, + (_requestId, mockRequest.requester, mockResponse.proposer, _paymentToken, _paymentAmount) + ), abi.encode() ); vm.startPrank(address(oracle)); - contractCallRequestModule.finalizeRequest(_request, mockResponse, address(oracle)); + contractCallRequestModule.finalizeRequest(mockRequest, mockResponse, address(oracle)); + } + + function test_finalizeWithoutResponse(IERC20 _paymentToken, uint256 _paymentAmount) public { + mockRequest.requestModuleData = abi.encode( + IContractCallRequestModule.RequestParameters({ + target: _targetContract, + functionSelector: _functionSelector, + data: _dataParams, + accountingExtension: accounting, + paymentToken: _paymentToken, + paymentAmount: _paymentAmount + }) + ); + + 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.release, (_requester, _requestId, _paymentToken, _paymentAmount)), + abi.encodeCall(IAccountingExtension.release, (mockRequest.requester, _requestId, _paymentToken, _paymentAmount)), abi.encode(true) ); - contractCallRequestModule.finalizeRequest(_request, mockResponse, address(this)); + vm.startPrank(address(oracle)); + contractCallRequestModule.finalizeRequest(mockRequest, mockResponse, address(oracle)); } - function test_emitsEvent( - bytes32 _requestId, - address _requester, - address _proposer, - IERC20 _paymentToken, - uint256 _paymentAmount, - IOracle.Request calldata _request - ) public { + function test_emitsEvent(IERC20 _paymentToken, uint256 _paymentAmount) public { // Use the correct accounting parameters - bytes memory _requestData = abi.encode( + mockRequest.requestModuleData = abi.encode( IContractCallRequestModule.RequestParameters({ target: _targetContract, functionSelector: _functionSelector, @@ -164,34 +172,31 @@ contract ContractCallRequestModule_Unit_FinalizeRequest is BaseTest { }) ); - // Mock and expect IAccountingExtension.pay to be called - _mockAndExpect( - address(accounting), - abi.encodeCall(IAccountingExtension.pay, (_requestId, _requester, _proposer, _paymentToken, _paymentAmount)), - abi.encode() - ); + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; - vm.startPrank(address(oracle)); - contractCallRequestModule.finalizeRequest(_request, mockResponse, address(oracle)); + // 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.release, (_requester, _requestId, _paymentToken, _paymentAmount)), + abi.encodeCall(IAccountingExtension.release, (mockRequest.requester, _requestId, _paymentToken, _paymentAmount)), abi.encode(true) ); // Check: is the event emitted? vm.expectEmit(true, true, true, true, address(contractCallRequestModule)); - emit RequestFinalized(_requestId, address(this)); + emit RequestFinalized(_requestId, mockResponse, address(this)); - contractCallRequestModule.finalizeRequest(_request, mockResponse, address(this)); + vm.prank(address(oracle)); + contractCallRequestModule.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, IOracle.Request calldata _request) public { vm.assume(_caller != address(oracle)); // Check: does it revert if not called by the Oracle? From 1ebbb639f589663dcaab64b2b88bfaf53164a03d Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sat, 11 Nov 2023 15:44:44 +0400 Subject: [PATCH 9/9] fix: onlyOracle modifier --- .../contracts/modules/request/ContractCallRequestModule.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solidity/contracts/modules/request/ContractCallRequestModule.sol b/solidity/contracts/modules/request/ContractCallRequestModule.sol index 578e552a..311487ec 100644 --- a/solidity/contracts/modules/request/ContractCallRequestModule.sol +++ b/solidity/contracts/modules/request/ContractCallRequestModule.sol @@ -21,7 +21,7 @@ contract ContractCallRequestModule is Module, IContractCallRequestModule { } /// @inheritdoc IContractCallRequestModule - 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({