From bf795a15507577304a4f9bee92ce9d29a7f2e66f Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Mon, 6 Nov 2023 19:30:08 +0400 Subject: [PATCH] perf: optimize `CircuitResolverModule` --- .../dispute/circuit_resolver_module.md | 2 +- .../modules/dispute/CircuitResolverModule.sol | 181 +++-- .../dispute/ICircuitResolverModule.sol | 121 +-- .../dispute/CircuitResolverModule.t.sol | 707 ++++++------------ 4 files changed, 382 insertions(+), 629 deletions(-) diff --git a/docs/src/content/modules/dispute/circuit_resolver_module.md b/docs/src/content/modules/dispute/circuit_resolver_module.md index 4806899e..4f6a89ad 100644 --- a/docs/src/content/modules/dispute/circuit_resolver_module.md +++ b/docs/src/content/modules/dispute/circuit_resolver_module.md @@ -10,7 +10,7 @@ The Circuit Resolver Module is a pre-dispute module that allows disputers to ver ### Key Methods -- `decodeRequestData(bytes32 _requestId)`: Returns the decoded data for a request. +- `decodeRequestData(bytes calldata _data)`: Returns the decoded data for a request. - `disputeResponse(bytes32 _requestId, bytes32 _responseId, address _disputer, address _proposer)`: Verifies the ZK circuit 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 circuit 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. diff --git a/solidity/contracts/modules/dispute/CircuitResolverModule.sol b/solidity/contracts/modules/dispute/CircuitResolverModule.sol index 015d8a76..c6c04652 100644 --- a/solidity/contracts/modules/dispute/CircuitResolverModule.sol +++ b/solidity/contracts/modules/dispute/CircuitResolverModule.sol @@ -1,91 +1,90 @@ -// // 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 {ICircuitResolverModule} from '../../../interfaces/modules/dispute/ICircuitResolverModule.sol'; - -// contract CircuitResolverModule is Module, ICircuitResolverModule { -// constructor(IOracle _oracle) Module(_oracle) {} - -// mapping(bytes32 _requestId => bytes _correctResponse) internal _correctResponses; - -// /// @inheritdoc IModule -// function moduleName() external pure returns (string memory _moduleName) { -// return 'CircuitResolverModule'; -// } - -// /// @inheritdoc ICircuitResolverModule -// function decodeRequestData(bytes32 _requestId) public view returns (RequestParameters memory _params) { -// _params = abi.decode(requestData[_requestId], (RequestParameters)); -// } - -// /// @inheritdoc ICircuitResolverModule -// function disputeEscalated(bytes32 _disputeId) external onlyOracle {} - -// /// @inheritdoc ICircuitResolverModule -// function onDisputeStatusChange(bytes32, /* _disputeId */ IOracle.Dispute memory _dispute) external onlyOracle { -// RequestParameters memory _params = decodeRequestData(_dispute.requestId); - -// IOracle.Response memory _response = ORACLE.getResponse(_dispute.responseId); - -// bytes memory _correctResponse = _correctResponses[_dispute.requestId]; -// bool _won = _response.response.length != _correctResponse.length -// || keccak256(_response.response) != keccak256(_correctResponse); - -// if (_won) { -// _params.accountingExtension.pay({ -// _requestId: _dispute.requestId, -// _payer: _dispute.proposer, -// _receiver: _dispute.disputer, -// _token: _params.bondToken, -// _amount: _params.bondSize -// }); -// bytes32 _correctResponseId = -// ORACLE.proposeResponse(_dispute.disputer, _dispute.requestId, abi.encode(_correctResponses[_dispute.requestId])); -// ORACLE.finalize(_dispute.requestId, _correctResponseId); -// } else { -// ORACLE.finalize(_dispute.requestId, _dispute.responseId); -// } - -// delete _correctResponses[_dispute.requestId]; - -// emit DisputeStatusChanged({ -// _requestId: _dispute.requestId, -// _responseId: _dispute.responseId, -// _disputer: _dispute.disputer, -// _proposer: _dispute.proposer, -// _status: _dispute.status -// }); -// } - -// /// @inheritdoc ICircuitResolverModule -// function disputeResponse( -// bytes32 _requestId, -// bytes32 _responseId, -// address _disputer, -// address _proposer -// ) external onlyOracle returns (IOracle.Dispute memory _dispute) { -// IOracle.Response memory _response = ORACLE.getResponse(_responseId); -// RequestParameters memory _params = decodeRequestData(_requestId); - -// (, bytes memory _correctResponse) = _params.verifier.call(_params.callData); -// _correctResponses[_requestId] = _correctResponse; - -// bool _won = _response.response.length != _correctResponse.length -// || keccak256(_response.response) != keccak256(_correctResponse); - -// _dispute = IOracle.Dispute({ -// disputer: _disputer, -// responseId: _responseId, -// proposer: _proposer, -// requestId: _requestId, -// status: _won ? IOracle.DisputeStatus.Won : IOracle.DisputeStatus.Lost, -// createdAt: block.timestamp -// }); - -// emit ResponseDisputed(_requestId, _responseId, _disputer, _proposer); -// } -// } +// 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 {ICircuitResolverModule} from '../../../interfaces/modules/dispute/ICircuitResolverModule.sol'; + +contract CircuitResolverModule is Module, ICircuitResolverModule { + constructor(IOracle _oracle) Module(_oracle) {} + + mapping(bytes32 _requestId => bytes _correctResponse) internal _correctResponses; + + /// @inheritdoc IModule + function moduleName() external pure returns (string memory _moduleName) { + return 'CircuitResolverModule'; + } + + /// @inheritdoc ICircuitResolverModule + function decodeRequestData(bytes calldata _data) public pure returns (RequestParameters memory _params) { + _params = abi.decode(_data, (RequestParameters)); + } + + /// @inheritdoc ICircuitResolverModule + function onDisputeStatusChange( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external onlyOracle { + // TODO: Call `disputeStatus` to check the current status + RequestParameters memory _params = decodeRequestData(_request.disputeModuleData); + + bytes memory _correctResponse = _correctResponses[_dispute.requestId]; + bool _won = _response.response.length != _correctResponse.length + || keccak256(_response.response) != keccak256(_correctResponse); + + if (_won) { + _params.accountingExtension.pay({ + _requestId: _dispute.requestId, + _payer: _dispute.proposer, + _receiver: _dispute.disputer, + _token: _params.bondToken, + _amount: _params.bondSize + }); + + IOracle.Response memory _newResponse = + IOracle.Response({requestId: _dispute.requestId, proposer: _dispute.disputer, response: _correctResponse}); + + ORACLE.proposeResponse(_dispute.disputer, _request, _newResponse); + ORACLE.finalize(_request, _newResponse); + } else { + ORACLE.finalize(_request, _response); + } + + delete _correctResponses[_dispute.requestId]; + + // TODO: Emit event + // emit DisputeStatusChanged({ + // _disputeId: _disputeId, + // _dispute: _dispute, + // _status: IOracle.DisputeStatus.Resolved + // }); + } + + /// @inheritdoc ICircuitResolverModule + function disputeResponse( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external onlyOracle { + RequestParameters memory _params = decodeRequestData(_request.disputeModuleData); + + (bool _success, bytes memory _correctResponse) = _params.verifier.call(_params.callData); + // TODO: Revert if !_success + _correctResponses[_response.requestId] = _correctResponse; + + bool _won = _response.response.length != _correctResponse.length + || keccak256(_response.response) != keccak256(_correctResponse); + + // TODO: call ORACLE.updateDisputeStatus + emit ResponseDisputed({ + _responseId: _dispute.responseId, + _disputeId: _getId(_dispute), + _dispute: _dispute, + _blockNumber: block.number + }); + } +} diff --git a/solidity/interfaces/modules/dispute/ICircuitResolverModule.sol b/solidity/interfaces/modules/dispute/ICircuitResolverModule.sol index fc224ce0..771cb87c 100644 --- a/solidity/interfaces/modules/dispute/ICircuitResolverModule.sol +++ b/solidity/interfaces/modules/dispute/ICircuitResolverModule.sol @@ -1,68 +1,69 @@ -// // 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 {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; -// import {IDisputeModule} from -// '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/dispute/IDisputeModule.sol'; +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; +import {IDisputeModule} from + '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/dispute/IDisputeModule.sol'; -// import {IAccountingExtension} from '../../extensions/IAccountingExtension.sol'; +import {IAccountingExtension} from '../../extensions/IAccountingExtension.sol'; -// /** -// * @title CircuitResolverModule -// * @notice Module allowing users to dispute a proposed response -// * by bonding tokens. -// * The module will invoke the circuit verifier supplied to calculate -// * the proposed response and compare it to the correct response. -// * - If the dispute is valid, the disputer wins and their bond is returned along with a reward. -// * - If the dispute is invalid, the bond is forfeited and returned to the proposer. -// * -// * After the dispute is settled, the correct response is automatically proposed to the oracle -// * and the request is finalized. -// */ -// interface ICircuitResolverModule is IDisputeModule { -// /*/////////////////////////////////////////////////////////////// -// STRUCTS -// //////////////////////////////////////////////////////////////*/ +/** + * @title CircuitResolverModule + * @notice Module allowing users to dispute a proposed response + * by bonding tokens. + * The module will invoke the circuit verifier supplied to calculate + * the proposed response and compare it to the correct response. + * - If the dispute is valid, the disputer wins and their bond is returned along with a reward. + * - If the dispute is invalid, the bond is forfeited and returned to the proposer. + * + * After the dispute is settled, the correct response is automatically proposed to the oracle + * and the request is finalized. + */ +interface ICircuitResolverModule is IDisputeModule { + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Parameters of the request as stored in the module -// * @return callData The encoded data forwarded to the verifier -// * @return verifier The address of the verifier contract -// * @return accountingExtension The address of the accounting extension -// * @return bondToken The address of the bond token -// * @return bondSize The size of the bond -// */ -// struct RequestParameters { -// bytes callData; -// address verifier; -// IAccountingExtension accountingExtension; -// IERC20 bondToken; -// uint256 bondSize; -// } + /** + * @notice Parameters of the request as stored in the module + * @return callData The encoded data forwarded to the verifier + * @return verifier The address of the verifier contract + * @return accountingExtension The address of the accounting extension + * @return bondToken The address of the bond token + * @return bondSize The size of the bond + */ + struct RequestParameters { + bytes callData; + address verifier; + IAccountingExtension accountingExtension; + IERC20 bondToken; + uint256 bondSize; + } -// /*/////////////////////////////////////////////////////////////// -// LOGIC -// //////////////////////////////////////////////////////////////*/ + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Returns the decoded data for a request -// * @param _requestId The ID of the request -// * @return _params The decoded parameters of 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 decoded parameters of the request + */ + function decodeRequestData(bytes calldata _data) external view returns (RequestParameters memory _params); -// /// @inheritdoc IDisputeModule -// function disputeResponse( -// bytes32 _requestId, -// bytes32 _responseId, -// address _disputer, -// address _proposer -// ) external returns (IOracle.Dispute memory _dispute); + /// @inheritdoc IDisputeModule + function disputeResponse( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external; -// /// @inheritdoc IDisputeModule -// function onDisputeStatusChange(bytes32 _disputeId, IOracle.Dispute memory _dispute) external; - -// /// @inheritdoc IDisputeModule -// function disputeEscalated(bytes32 _disputeId) external; -// } + /// @inheritdoc IDisputeModule + function onDisputeStatusChange( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external; +} diff --git a/solidity/test/unit/modules/dispute/CircuitResolverModule.t.sol b/solidity/test/unit/modules/dispute/CircuitResolverModule.t.sol index 36e325fa..6a026545 100644 --- a/solidity/test/unit/modules/dispute/CircuitResolverModule.t.sol +++ b/solidity/test/unit/modules/dispute/CircuitResolverModule.t.sol @@ -1,477 +1,230 @@ -// // 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 { -// CircuitResolverModule, -// ICircuitResolverModule -// } from '../../../../contracts/modules/dispute/CircuitResolverModule.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_CircuitResolverModule is CircuitResolverModule { -// constructor(IOracle _oracle) CircuitResolverModule(_oracle) {} - -// function forTest_setRequestData(bytes32 _requestId, bytes memory _data) public { -// requestData[_requestId] = _data; -// } - -// function forTest_setCorrectResponse(bytes32 _requestId, bytes memory _data) public { -// _correctResponses[_requestId] = _data; -// } -// } - -// /** -// * @title Bonded Dispute Module Unit tests -// */ -// contract BaseTest is Test, Helpers { -// // The target contract -// ForTest_CircuitResolverModule public circuitResolverModule; -// // A mock oracle -// IOracle public oracle; -// // A mock accounting extension -// IAccountingExtension public accountingExtension; -// // Some unnoticeable dude -// address public dude = makeAddr('dude'); -// // 100% random sequence of bytes representing request, response, or dispute id -// bytes32 public mockId = bytes32('69'); -// // Create a new dummy dispute -// IOracle.Dispute public mockDispute; -// // A mock circuit verifier address -// address public circuitVerifier; -// // Mock addresses -// IERC20 public _token = IERC20(makeAddr('token')); -// address public _disputer = makeAddr('disputer'); -// address public _proposer = makeAddr('proposer'); -// bytes internal _callData = abi.encodeWithSignature('test(uint256)', 123); - -// event ResponseDisputed(bytes32 indexed _requestId, bytes32 _responseId, address _disputer, address _proposer); -// event DisputeStatusChanged( -// bytes32 indexed _requestId, bytes32 _responseId, address _disputer, address _proposer, IOracle.DisputeStatus _status -// ); - -// /** -// * @notice Deploy the target and mock oracle+accounting extension -// */ -// function setUp() public { -// oracle = IOracle(makeAddr('Oracle')); -// vm.etch(address(oracle), hex'069420'); - -// accountingExtension = IAccountingExtension(makeAddr('AccountingExtension')); -// vm.etch(address(accountingExtension), hex'069420'); -// circuitVerifier = makeAddr('CircuitVerifier'); -// vm.etch(address(circuitVerifier), hex'069420'); - -// circuitResolverModule = new ForTest_CircuitResolverModule(oracle); - -// mockDispute = IOracle.Dispute({ -// createdAt: block.timestamp, -// disputer: dude, -// responseId: mockId, -// proposer: dude, -// requestId: mockId, -// status: IOracle.DisputeStatus.Active -// }); -// } -// } - -// contract CircuitResolverModule_Unit_ModuleData is BaseTest { -// /** -// * @notice Test that the decodeRequestData function returns the correct values -// */ -// function test_decodeRequestData_returnsCorrectData( -// bytes32 _requestId, -// address _accountingExtension, -// address _randomToken, -// uint256 _bondSize -// ) public { -// // Mock data -// bytes memory _requestData = abi.encode( -// ICircuitResolverModule.RequestParameters({ -// callData: _callData, -// verifier: circuitVerifier, -// accountingExtension: IAccountingExtension(_accountingExtension), -// bondToken: IERC20(_randomToken), -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// circuitResolverModule.forTest_setRequestData(_requestId, _requestData); - -// // Test: decode the given request data -// ICircuitResolverModule.RequestParameters memory _params = circuitResolverModule.decodeRequestData(_requestId); - -// // Check: is the request data properly stored? -// assertEq(_params.callData, _callData, 'Mismatch: decoded calldata'); -// assertEq(_params.verifier, circuitVerifier, 'Mismatch: decoded circuit verifier'); -// assertEq(address(_params.accountingExtension), _accountingExtension, 'Mismatch: decoded accounting extension'); -// assertEq(address(_params.bondToken), _randomToken, 'Mismatch: decoded token'); -// assertEq(_params.bondSize, _bondSize, 'Mismatch: decoded bond size'); -// } - -// /** -// * @notice Test that the moduleName function returns the correct name -// */ -// function test_moduleNameReturnsName() public { -// assertEq(circuitResolverModule.moduleName(), 'CircuitResolverModule'); -// } -// } - -// contract CircuitResolverModule_Unit_DisputeResponse is BaseTest { -// /** -// * @notice Test if dispute incorrect response returns the correct status -// */ -// function test_disputeIncorrectResponse(bytes32 _requestId, bytes32 _responseId, uint256 _bondSize) public { -// vm.assume(_requestId != _responseId); -// bool _correctResponse = false; - -// // Mock request data -// bytes memory _requestData = abi.encode( -// ICircuitResolverModule.RequestParameters({ -// callData: _callData, -// verifier: circuitVerifier, -// accountingExtension: accountingExtension, -// bondToken: IERC20(_token), -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// circuitResolverModule.forTest_setRequestData(_requestId, _requestData); - -// // Create new Response memory struct with random values -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// proposer: _proposer, -// requestId: _requestId, -// disputeId: mockId, -// response: abi.encode(true) -// }); - -// // Mock and expect the call to the oracle, getting the response -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponse, (_responseId)), abi.encode(_mockResponse)); - -// // Mock and expect the call to the verifier -// _mockAndExpect(circuitVerifier, _callData, abi.encode(_correctResponse)); - -// // Test: call disputeResponse -// vm.prank(address(oracle)); -// IOracle.Dispute memory _dispute = -// circuitResolverModule.disputeResponse(_requestId, _responseId, _disputer, _proposer); - -// // Check: is the dispute data properly stored? -// assertEq(_dispute.disputer, _disputer, 'Mismatch: disputer'); -// assertEq(_dispute.proposer, _proposer, 'Mismatch: proposer'); -// assertEq(_dispute.responseId, _responseId, 'Mismatch: responseId'); -// assertEq(_dispute.requestId, _requestId, 'Mismatch: requestId'); -// assertEq(uint256(_dispute.status), uint256(IOracle.DisputeStatus.Won), 'Mismatch: status'); -// assertEq(_dispute.createdAt, block.timestamp, 'Mismatch: createdAt'); -// } - -// function test_emitsEvent(bytes32 _requestId, bytes32 _responseId, uint256 _bondSize) public { -// vm.assume(_requestId != _responseId); - -// bool _correctResponse = false; - -// // Mock request data -// bytes memory _requestData = abi.encode( -// ICircuitResolverModule.RequestParameters({ -// callData: _callData, -// verifier: circuitVerifier, -// accountingExtension: accountingExtension, -// bondToken: IERC20(_token), -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// circuitResolverModule.forTest_setRequestData(_requestId, _requestData); - -// // Create new Response memory struct with random values -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// proposer: _proposer, -// requestId: _requestId, -// disputeId: mockId, -// response: abi.encode(true) -// }); - -// // Mock and expect the call to the oracle, getting the response -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponse, (_responseId)), abi.encode(_mockResponse)); - -// // Mock and expect the call to the verifier -// _mockAndExpect(circuitVerifier, _callData, abi.encode(_correctResponse)); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(circuitResolverModule)); -// emit ResponseDisputed(_requestId, _responseId, _disputer, _proposer); - -// vm.prank(address(oracle)); -// circuitResolverModule.disputeResponse(_requestId, _responseId, _disputer, _proposer); -// } - -// /** -// * @notice Test if dispute correct response returns the correct status -// */ -// function test_disputeCorrectResponse(bytes32 _requestId, bytes32 _responseId, uint256 _bondSize) public { -// vm.assume(_requestId != _responseId); - -// bytes memory _encodedCorrectResponse = abi.encode(true); - -// // Mock request data -// bytes memory _requestData = abi.encode( -// ICircuitResolverModule.RequestParameters({ -// callData: _callData, -// verifier: circuitVerifier, -// accountingExtension: accountingExtension, -// bondToken: IERC20(_token), -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// circuitResolverModule.forTest_setRequestData(_requestId, _requestData); - -// // Create new Response memory struct with random values -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// proposer: _proposer, -// requestId: _requestId, -// disputeId: mockId, -// response: _encodedCorrectResponse -// }); - -// // Mock and expect the call to the oracle, getting the response -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponse, (_responseId)), abi.encode(_mockResponse)); - -// // Mock and expect the call to the verifier -// _mockAndExpect(circuitVerifier, _callData, _encodedCorrectResponse); - -// vm.prank(address(oracle)); -// IOracle.Dispute memory _dispute = -// circuitResolverModule.disputeResponse(_requestId, _responseId, _disputer, _proposer); - -// // Check: is the dispute data properly stored? -// assertEq(_dispute.disputer, _disputer, 'Mismatch: disputer'); -// assertEq(_dispute.proposer, _proposer, 'Mismatch: proposer'); -// assertEq(_dispute.responseId, _responseId, 'Mismatch: responseId'); -// assertEq(_dispute.requestId, _requestId, 'Mismatch: requestId'); -// assertEq(uint256(_dispute.status), uint256(IOracle.DisputeStatus.Lost), 'Mismatch: status'); -// assertEq(_dispute.createdAt, block.timestamp, 'Mismatch: createdAt'); -// } - -// /** -// * @notice Test if dispute response reverts when called by caller who's not the oracle -// */ -// function test_revertWrongCaller(address _randomCaller) public { -// vm.assume(_randomCaller != address(oracle)); - -// // Check: does it revert if not called by the Oracle? -// vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); - -// vm.prank(_randomCaller); -// circuitResolverModule.disputeResponse(mockId, mockId, dude, dude); -// } -// } - -// contract CircuitResolverModule_Unit_DisputeEscalation is BaseTest { -// /** -// * @notice Test if dispute escalated do nothing -// */ -// function test_returnCorrectStatus() public { -// // Record sstore and sload -// vm.prank(address(oracle)); -// vm.record(); -// circuitResolverModule.disputeEscalated(mockId); -// (bytes32[] memory _reads, bytes32[] memory _writes) = vm.accesses(address(circuitResolverModule)); - -// // Check: no storage access? -// assertEq(_reads.length, 0); -// assertEq(_writes.length, 0); -// } - -// /** -// * @notice Test that escalateDispute finalizes the request if the original response is correct -// */ -// function test_correctResponse(bytes32 _requestId, bytes32 _responseId, uint256 _bondSize) public { -// vm.assume(_requestId != _responseId); - -// bytes memory _encodedCorrectResponse = abi.encode(true); - -// // Mock request data -// bytes memory _requestData = abi.encode( -// ICircuitResolverModule.RequestParameters({ -// callData: _callData, -// verifier: circuitVerifier, -// accountingExtension: accountingExtension, -// bondToken: IERC20(_token), -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// circuitResolverModule.forTest_setRequestData(_requestId, _requestData); - -// circuitResolverModule.forTest_setCorrectResponse(_requestId, _encodedCorrectResponse); - -// // Create new Response memory struct with random values -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// proposer: _proposer, -// requestId: _requestId, -// disputeId: mockId, -// response: _encodedCorrectResponse -// }); - -// // Mock and expect the call to the oracle, getting the response -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponse, (_responseId)), abi.encode(_mockResponse)); - -// // Mock and expect the call to the oracle, finalizing the request -// _mockAndExpect( -// address(oracle), abi.encodeWithSignature('finalize(bytes32,bytes32)', _requestId, _responseId), abi.encode() -// ); - -// // Populate the mock dispute with the correct values -// mockDispute.status = IOracle.DisputeStatus.Lost; -// mockDispute.responseId = _responseId; -// mockDispute.requestId = _requestId; - -// vm.prank(address(oracle)); -// circuitResolverModule.onDisputeStatusChange(bytes32(0), mockDispute); -// } - -// /** -// * @notice Test that escalateDispute pays the disputer and proposes the new response -// */ -// function test_incorrectResponse(bytes32 _requestId, bytes32 _responseId, uint256 _bondSize) public { -// vm.assume(_requestId != _responseId); - -// bytes32 _correctResponseId = bytes32(uint256(mockId) + 2); -// bytes memory _encodedCorrectResponse = abi.encode(true); - -// // Mock request data -// bytes memory _requestData = abi.encode( -// ICircuitResolverModule.RequestParameters({ -// callData: _callData, -// verifier: circuitVerifier, -// accountingExtension: accountingExtension, -// bondToken: IERC20(_token), -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request and correct response -// circuitResolverModule.forTest_setRequestData(_requestId, _requestData); -// circuitResolverModule.forTest_setCorrectResponse(_requestId, _encodedCorrectResponse); - -// // Create new Response memory struct with random values -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// proposer: _proposer, -// requestId: _requestId, -// disputeId: mockId, -// response: abi.encode(false) -// }); - -// // Mock and expect the call to the oracle, getting the response -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponse, (_responseId)), abi.encode(_mockResponse)); - -// // Mock and expect the call to the accounting extension, paying the disputer -// _mockAndExpect( -// address(accountingExtension), -// abi.encodeCall(accountingExtension.pay, (_requestId, _proposer, _disputer, _token, _bondSize)), -// abi.encode() -// ); - -// // Mock and expect the call to the oracle, proposing the correct response with the disputer as the new proposer -// _mockAndExpect( -// address(oracle), -// abi.encodeWithSignature( -// 'proposeResponse(address,bytes32,bytes)', _disputer, _requestId, abi.encode(_encodedCorrectResponse) -// ), -// abi.encode(_correctResponseId) -// ); - -// // Mock and expect the call to the oracle, finalizing the request with the correct response -// _mockAndExpect( -// address(oracle), -// abi.encodeWithSignature('finalize(bytes32,bytes32)', _requestId, _correctResponseId), -// abi.encode() -// ); - -// // Populate the mock dispute with the correct values -// mockDispute.status = IOracle.DisputeStatus.Won; -// mockDispute.responseId = _responseId; -// mockDispute.requestId = _requestId; -// mockDispute.disputer = _disputer; -// mockDispute.proposer = _proposer; - -// vm.prank(address(oracle)); -// circuitResolverModule.onDisputeStatusChange(bytes32(0), mockDispute); -// } -// } - -// contract CircuitResolverModule_Unit_OnDisputeStatusChange is BaseTest { -// function test_eventEmitted(bytes32 _requestId, bytes32 _responseId, uint256 _bondSize) public { -// vm.assume(_requestId != _responseId); - -// bytes memory _encodedCorrectResponse = abi.encode(true); - -// // Mock request data -// bytes memory _requestData = abi.encode( -// ICircuitResolverModule.RequestParameters({ -// callData: _callData, -// verifier: circuitVerifier, -// accountingExtension: accountingExtension, -// bondToken: IERC20(_token), -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// circuitResolverModule.forTest_setRequestData(_requestId, _requestData); -// circuitResolverModule.forTest_setCorrectResponse(_requestId, _encodedCorrectResponse); - -// // Create new Response memory struct with random values -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// proposer: _proposer, -// requestId: _requestId, -// disputeId: mockId, -// response: _encodedCorrectResponse -// }); - -// // Mock and expect the call to the oracle, getting the response -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponse, (_responseId)), abi.encode(_mockResponse)); - -// // Mock and expect the call to the oracle, finalizing the request -// _mockAndExpect( -// address(oracle), abi.encodeWithSignature('finalize(bytes32,bytes32)', _requestId, _responseId), abi.encode() -// ); - -// // Populate the mock dispute with the correct values -// mockDispute.status = IOracle.DisputeStatus.Lost; -// mockDispute.responseId = _responseId; -// mockDispute.requestId = _requestId; - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(circuitResolverModule)); -// emit DisputeStatusChanged( -// _requestId, _responseId, mockDispute.disputer, mockDispute.proposer, IOracle.DisputeStatus.Lost -// ); - -// vm.prank(address(oracle)); -// circuitResolverModule.onDisputeStatusChange(bytes32(0), mockDispute); -// } -// } +// 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 { + CircuitResolverModule, + ICircuitResolverModule +} from '../../../../contracts/modules/dispute/CircuitResolverModule.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_CircuitResolverModule is CircuitResolverModule { + constructor(IOracle _oracle) CircuitResolverModule(_oracle) {} + + function forTest_setCorrectResponse(bytes32 _requestId, bytes memory _data) public { + _correctResponses[_requestId] = _data; + } +} + +/** + * @title Bonded Dispute Module Unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + ForTest_CircuitResolverModule public circuitResolverModule; + // A mock oracle + IOracle public oracle; + // A mock accounting extension + IAccountingExtension public accountingExtension; + + // A mock circuit verifier address + address public circuitVerifier; + // 100% random sequence of bytes representing request, response, or dispute id + bytes32 public mockId = bytes32('69'); + + // Create a new dummy response + IOracle.Response public mockResponse; + // Create a new dummy dispute + IOracle.Dispute public mockDispute; + + // Mock addresses + IERC20 public _token = IERC20(makeAddr('token')); + address public _disputer = makeAddr('disputer'); + address public _proposer = makeAddr('proposer'); + bytes internal _callData = abi.encodeWithSignature('test(uint256)', 123); + + event DisputeStatusChanged(bytes32 _disputeId, IOracle.Dispute _dispute, IOracle.DisputeStatus _status); + event ResponseDisputed( + bytes32 indexed _responseId, bytes32 indexed _disputeId, IOracle.Dispute _dispute, uint256 _blockNumber + ); + + /** + * @notice Deploy the target and mock oracle+accounting extension + */ + function setUp() public { + oracle = IOracle(makeAddr('Oracle')); + vm.etch(address(oracle), hex'069420'); + + accountingExtension = IAccountingExtension(makeAddr('AccountingExtension')); + vm.etch(address(accountingExtension), hex'069420'); + circuitVerifier = makeAddr('CircuitVerifier'); + vm.etch(address(circuitVerifier), hex'069420'); + + circuitResolverModule = new ForTest_CircuitResolverModule(oracle); + + mockDispute = IOracle.Dispute({disputer: _disputer, responseId: mockId, proposer: _proposer, requestId: mockId}); + + mockResponse = IOracle.Response({proposer: _proposer, requestId: mockId, response: bytes('')}); + } +} + +contract CircuitResolverModule_Unit_ModuleData is BaseTest { + /** + * @notice Test that the decodeRequestData function returns the correct values + */ + function test_decodeRequestData_returnsCorrectData( + address _accountingExtension, + address _randomToken, + uint256 _bondSize + ) public { + // Mock data + bytes memory _requestData = abi.encode( + ICircuitResolverModule.RequestParameters({ + callData: _callData, + verifier: circuitVerifier, + accountingExtension: IAccountingExtension(_accountingExtension), + bondToken: IERC20(_randomToken), + bondSize: _bondSize + }) + ); + + // Test: decode the given request data + ICircuitResolverModule.RequestParameters memory _params = circuitResolverModule.decodeRequestData(_requestData); + + // Check: is the request data properly stored? + assertEq(_params.callData, _callData, 'Mismatch: decoded calldata'); + assertEq(_params.verifier, circuitVerifier, 'Mismatch: decoded circuit verifier'); + assertEq(address(_params.accountingExtension), _accountingExtension, 'Mismatch: decoded accounting extension'); + assertEq(address(_params.bondToken), _randomToken, 'Mismatch: decoded token'); + assertEq(_params.bondSize, _bondSize, 'Mismatch: decoded bond size'); + } + + /** + * @notice Test that the moduleName function returns the correct name + */ + function test_moduleNameReturnsName() public { + assertEq(circuitResolverModule.moduleName(), 'CircuitResolverModule'); + } +} + +contract CircuitResolverModule_Unit_DisputeResponse is BaseTest { + /** + * @notice Test if dispute incorrect response returns the correct status + */ + function test_disputeIncorrectResponse(IOracle.Request calldata _request) public { + bytes32 _requestId = _getId(_request); + bool _correctResponse = false; + + // Create new Response memory struct with random values + IOracle.Response memory _response = + IOracle.Response({proposer: _proposer, requestId: _requestId, response: abi.encode(true)}); + + mockDispute.requestId = _requestId; + mockDispute.responseId = _getId(_request); + + // Mock and expect the call to the verifier + _mockAndExpect(circuitVerifier, _callData, abi.encode(_correctResponse)); + + // Test: call disputeResponse + vm.prank(address(oracle)); + circuitResolverModule.disputeResponse(_request, _response, mockDispute); + } + + function test_emitsEvent(IOracle.Request calldata _request, uint256 _bondSize) public { + bytes32 _requestId = _getId(_request); + bool _correctResponse = false; + + mockResponse.requestId = _requestId; + mockResponse.response = abi.encode(true); + + // Mock and expect the call to the verifier + _mockAndExpect(circuitVerifier, _callData, abi.encode(_correctResponse)); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(circuitResolverModule)); + emit ResponseDisputed({ + _responseId: mockDispute.responseId, + _disputeId: _getId(mockDispute), + _dispute: mockDispute, + _blockNumber: block.number + }); + + vm.prank(address(oracle)); + circuitResolverModule.disputeResponse(_request, mockResponse, mockDispute); + } + + /** + * @notice Test if dispute correct response returns the correct status + */ + function test_disputeCorrectResponse( + IOracle.Request calldata _request, + bytes32 _responseId, + uint256 _bondSize + ) public { + bytes32 _requestId = _getId(_request); + bytes memory _encodedCorrectResponse = abi.encode(true); + + // Create new Response memory struct with random values + mockResponse.requestId = _requestId; + mockResponse.response = _encodedCorrectResponse; + + // Mock and expect the call to the verifier + _mockAndExpect(circuitVerifier, _callData, _encodedCorrectResponse); + + vm.prank(address(oracle)); + circuitResolverModule.disputeResponse(_request, mockResponse, mockDispute); + } + + /** + * @notice Test if dispute response reverts when called by caller who's not the oracle + */ + function test_revertWrongCaller(address _randomCaller, IOracle.Request calldata _request) public { + vm.assume(_randomCaller != address(oracle)); + + // Check: does it revert if not called by the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(_randomCaller); + circuitResolverModule.disputeResponse(_request, mockResponse, mockDispute); + } +} + +contract CircuitResolverModule_Unit_OnDisputeStatusChange is BaseTest { + function test_eventEmitted(IOracle.Request calldata _request, bytes32 _responseId, uint256 _bondSize) public { + bytes32 _requestId = _getId(_request); + bytes memory _encodedCorrectResponse = abi.encode(true); + + circuitResolverModule.forTest_setCorrectResponse(_requestId, _encodedCorrectResponse); + + mockResponse.requestId = _requestId; + mockResponse.response = _encodedCorrectResponse; + mockResponse.proposer = mockDispute.disputer; + + // Mock and expect the call to the oracle, finalizing the request + _mockAndExpect(address(oracle), abi.encodeCall(IOracle.finalize, (_request, mockResponse)), abi.encode(true)); + + // Populate the mock dispute with the correct values + mockDispute.responseId = _responseId; + mockDispute.requestId = _requestId; + bytes32 _disputeId = _getId(mockDispute); + IOracle.DisputeStatus _status = IOracle.DisputeStatus.Lost; + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(circuitResolverModule)); + emit DisputeStatusChanged(_disputeId, mockDispute, _status); + + vm.prank(address(oracle)); + circuitResolverModule.onDisputeStatusChange(_disputeId, _request, mockResponse, mockDispute); + } +}