From 2866715e801ac2d0f180ea9ba2893b057701b2b1 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:02:42 +0400 Subject: [PATCH] fix: improve logic in `RootVerificationModule` and fix tests --- .../dispute/RootVerificationModule.sol | 19 +- .../dispute/IRootVerificationModule.sol | 28 +- .../dispute/RootVerificationModule.t.sol | 642 +++++++++--------- 3 files changed, 334 insertions(+), 355 deletions(-) diff --git a/solidity/contracts/modules/dispute/RootVerificationModule.sol b/solidity/contracts/modules/dispute/RootVerificationModule.sol index 61f349e0..e954fc1a 100644 --- a/solidity/contracts/modules/dispute/RootVerificationModule.sol +++ b/solidity/contracts/modules/dispute/RootVerificationModule.sol @@ -56,20 +56,16 @@ contract RootVerificationModule is Module, IRootVerificationModule { response: abi.encode(_correctRoot) }); - ORACLE.proposeResponse(_dispute.disputer, _request, _newResponse); + emit DisputeStatusChanged({_disputeId: _disputeId, _dispute: _dispute, _status: IOracle.DisputeStatus.Won}); + + ORACLE.proposeResponse(_request, _newResponse); ORACLE.finalize(_request, _newResponse); } else { + emit DisputeStatusChanged({_disputeId: _disputeId, _dispute: _dispute, _status: IOracle.DisputeStatus.Lost}); ORACLE.finalize(_request, _response); } delete _correctRoots[_dispute.requestId]; - - // TODO: Emit event - // emit DisputeStatusChanged({ - // _disputeId: _disputeId, - // _dispute: _dispute, - // _status: IOracle.DisputeStatus.Resolved - // }); } /// @inheritdoc IRootVerificationModule @@ -83,14 +79,17 @@ contract RootVerificationModule is Module, IRootVerificationModule { bytes32 _correctRoot = _params.treeVerifier.calculateRoot(_params.treeData, _params.leavesToInsert); _correctRoots[_response.requestId] = _correctRoot; - bool _won = abi.decode(_response.response, (bytes32)) != _correctRoot; + IOracle.DisputeStatus _status = + abi.decode(_response.response, (bytes32)) != _correctRoot ? IOracle.DisputeStatus.Won : IOracle.DisputeStatus.Lost; - // TODO: call ORACLE.updateDisputeStatus emit ResponseDisputed({ + _requestId: _response.requestId, _responseId: _dispute.responseId, _disputeId: _getId(_dispute), _dispute: _dispute, _blockNumber: block.number }); + + ORACLE.updateDisputeStatus(_request, _response, _dispute, _status); } } diff --git a/solidity/interfaces/modules/dispute/IRootVerificationModule.sol b/solidity/interfaces/modules/dispute/IRootVerificationModule.sol index 26177064..f29c92f1 100644 --- a/solidity/interfaces/modules/dispute/IRootVerificationModule.sol +++ b/solidity/interfaces/modules/dispute/IRootVerificationModule.sol @@ -11,11 +11,10 @@ import {IAccountingExtension} from '../../extensions/IAccountingExtension.sol'; /* * @title RootVerificationModule - * @notice Dispute module allowing disputers to calculate the correct root - * for a given request and propose it as a response. If the disputer wins the - * dispute, he is rewarded with the bond of the proposer. - * @dev This module is a pre-dispute module. It allows disputing - * and resolving a response in a single call. + * @notice Dispute module allowing disputers to calculate the correct root for a given request and propose it as a response. + * If the disputer wins the dispute, he is rewarded with the bond of the proposer. + * + * @dev This module is a pre-dispute module. It allows disputing and resolving a response in a single call. */ interface IRootVerificationModule is IDisputeModule { /*/////////////////////////////////////////////////////////////// @@ -50,14 +49,29 @@ interface IRootVerificationModule is IDisputeModule { */ function decodeRequestData(bytes calldata _data) external view returns (RequestParameters memory _params); - /// @inheritdoc IDisputeModule + /** + * @notice Initiates and resolves the dispute by comparing the proposed response with the one returned by the verifier + * + * @dev This function will notify the oracle about the outcome of the dispute + * @param _request The request that the response was proposed to + * @param _response The response that is being disputed + * @param _dispute The dispute created by the oracle + */ function disputeResponse( IOracle.Request calldata _request, IOracle.Response calldata _response, IOracle.Dispute calldata _dispute ) external; - /// @inheritdoc IDisputeModule + /** + * @notice Depending on the status of the dispute, either pays the disputer and submits the correct response, + * or pays the proposer. Finalizes the request in any case. + * + * @param _disputeId The id of the dispute + * @param _request The request that the response was proposed to + * @param _response The response that was disputed + * @param _dispute The dispute + */ function onDisputeStatusChange( bytes32 _disputeId, IOracle.Request calldata _request, diff --git a/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol b/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol index 98837592..7959bd10 100644 --- a/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol +++ b/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol @@ -1,338 +1,304 @@ -// // 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 { -// RootVerificationModule, -// IRootVerificationModule -// } from '../../../../contracts/modules/dispute/RootVerificationModule.sol'; - -// import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountingExtension.sol'; -// import {ITreeVerifier} from '../../../../interfaces/ITreeVerifier.sol'; - -// /** -// * @dev Harness to set an entry in the requestData mapping, without triggering setup request hooks -// */ -// contract ForTest_RootVerificationModule is RootVerificationModule { -// constructor(IOracle _oracle) RootVerificationModule(_oracle) {} - -// function forTest_setRequestData(bytes32 _requestId, bytes memory _data) public { -// requestData[_requestId] = _data; -// } -// } - -// /** -// * @title Root Verification Module Unit tests -// */ -// contract BaseTest is Test, Helpers { -// // The target contract -// ForTest_RootVerificationModule public rootVerificationModule; -// // 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 tree verifier -// ITreeVerifier public treeVerifier; -// // Mock addresses -// IERC20 public _token = IERC20(makeAddr('token')); -// address public _disputer = makeAddr('disputer'); -// address public _proposer = makeAddr('proposer'); - -// // Mock request data -// bytes32[32] internal _treeBranches = [ -// bytes32('branch1'), -// bytes32('branch2'), -// bytes32('branch3'), -// bytes32('branch4'), -// bytes32('branch5'), -// bytes32('branch6'), -// bytes32('branch7'), -// bytes32('branch8'), -// bytes32('branch9'), -// bytes32('branch10'), -// bytes32('branch11'), -// bytes32('branch12'), -// bytes32('branch13'), -// bytes32('branch14'), -// bytes32('branch15'), -// bytes32('branch16'), -// bytes32('branch17'), -// bytes32('branch18'), -// bytes32('branch19'), -// bytes32('branch20'), -// bytes32('branch21'), -// bytes32('branch22'), -// bytes32('branch23'), -// bytes32('branch24'), -// bytes32('branch25'), -// bytes32('branch26'), -// bytes32('branch27'), -// bytes32('branch28'), -// bytes32('branch29'), -// bytes32('branch30'), -// bytes32('branch31'), -// bytes32('branch32') -// ]; -// uint256 internal _treeCount = 1; -// bytes internal _treeData = abi.encode(_treeBranches, _treeCount); -// bytes32[] internal _leavesToInsert = [bytes32('leave1'), bytes32('leave2')]; - -// event ResponseDisputed(bytes32 indexed _requestId, bytes32 _responseId, address _disputer, address _proposer); - -// /** -// * @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'); -// treeVerifier = ITreeVerifier(makeAddr('TreeVerifier')); -// vm.etch(address(treeVerifier), hex'069420'); - -// rootVerificationModule = new ForTest_RootVerificationModule(oracle); - -// mockDispute = IOracle.Dispute({ -// disputer: dude, -// responseId: mockId, -// proposer: dude, -// requestId: mockId, - -// }); -// } -// } - -// contract RootVerificationModule_Unit_ModuleData is BaseTest { -// /** -// * @notice Test that the moduleName function returns the correct name -// */ -// function test_moduleNameReturnsName() public { -// assertEq(rootVerificationModule.moduleName(), 'RootVerificationModule'); -// } - -// /** -// * @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( -// IRootVerificationModule.RequestParameters({ -// treeData: _treeData, -// leavesToInsert: _leavesToInsert, -// treeVerifier: treeVerifier, -// accountingExtension: IAccountingExtension(_accountingExtension), -// bondToken: IERC20(_randomToken), -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// rootVerificationModule.forTest_setRequestData(_requestId, _requestData); - -// IRootVerificationModule.RequestParameters memory _params = rootVerificationModule.decodeRequestData(_requestId); - -// bytes32[32] memory _treeBranchesStored; -// uint256 _treeCountStored; -// (_treeBranchesStored, _treeCountStored) = abi.decode(_params.treeData, (bytes32[32], uint256)); - -// // Check: is the request data properly stored? -// for (uint256 _i = 0; _i < _treeBranches.length; _i++) { -// assertEq(_treeBranchesStored[_i], _treeBranches[_i], 'Mismatch: decoded tree branch'); -// } -// for (uint256 _i = 0; _i < _leavesToInsert.length; _i++) { -// assertEq(_params.leavesToInsert[_i], _leavesToInsert[_i], 'Mismatch: decoded leave to insert'); -// } -// assertEq(_treeCountStored, _treeCount, 'Mismatch: decoded tree count'); -// assertEq(address(_params.treeVerifier), address(treeVerifier), 'Mismatch: decoded tree 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'); -// } -// } - -// contract RootVerificationModule_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); - -// // Mock request data -// bytes memory _requestData = abi.encode( -// IRootVerificationModule.RequestParameters({ -// treeData: _treeData, -// leavesToInsert: _leavesToInsert, -// treeVerifier: treeVerifier, -// accountingExtension: accountingExtension, -// bondToken: _token, -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// rootVerificationModule.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(bytes32('randomRoot')) -// }); - -// // 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 tree verifier, calculating the root -// _mockAndExpect( -// address(treeVerifier), -// abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), -// abi.encode(bytes32('randomRoot2')) -// ); - -// vm.prank(address(oracle)); -// IOracle.Dispute memory _dispute = -// rootVerificationModule.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); - -// // Mock request data -// bytes memory _requestData = abi.encode( -// IRootVerificationModule.RequestParameters({ -// treeData: _treeData, -// leavesToInsert: _leavesToInsert, -// treeVerifier: treeVerifier, -// accountingExtension: accountingExtension, -// bondToken: _token, -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// rootVerificationModule.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(bytes32('randomRoot')) -// }); - -// // 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 tree verifier, calculating the root -// _mockAndExpect( -// address(treeVerifier), -// abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), -// abi.encode(bytes32('randomRoot2')) -// ); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(rootVerificationModule)); -// emit ResponseDisputed({_requestId: _dispute.requestId, _responseId: _dispute.responseId, _dispute: _dispute, blockNumber: block.number}); - -// vm.prank(address(oracle)); -// rootVerificationModule.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 _encodedCorrectRoot = abi.encode(bytes32('randomRoot')); - -// // Mock request data -// bytes memory _requestData = abi.encode( -// IRootVerificationModule.RequestParameters({ -// treeData: _treeData, -// leavesToInsert: _leavesToInsert, -// treeVerifier: treeVerifier, -// accountingExtension: accountingExtension, -// bondToken: _token, -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// rootVerificationModule.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: _encodedCorrectRoot -// }); - -// // 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 tree verifier, calculating the root -// _mockAndExpect( -// address(treeVerifier), -// abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), -// _encodedCorrectRoot -// ); - -// vm.prank(address(oracle)); -// IOracle.Dispute memory _dispute = -// rootVerificationModule.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: revert if not called by the Oracle? -// vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); - -// vm.prank(_randomCaller); -// rootVerificationModule.disputeResponse(mockId, mockId, dude, dude); -// } -// } +// 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 { + RootVerificationModule, + IRootVerificationModule +} from '../../../../contracts/modules/dispute/RootVerificationModule.sol'; + +import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountingExtension.sol'; +import {ITreeVerifier} from '../../../../interfaces/ITreeVerifier.sol'; + +/** + * @title Root Verification Module Unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + RootVerificationModule public rootVerificationModule; + // A mock oracle + IOracle public oracle; + // A mock accounting extension + IAccountingExtension public accountingExtension; + // A mock tree verifier + ITreeVerifier public treeVerifier; + // Mock addresses + IERC20 public _token = IERC20(makeAddr('token')); + + // Mock request data + bytes32[32] internal _treeBranches = [ + bytes32('branch1'), + bytes32('branch2'), + bytes32('branch3'), + bytes32('branch4'), + bytes32('branch5'), + bytes32('branch6'), + bytes32('branch7'), + bytes32('branch8'), + bytes32('branch9'), + bytes32('branch10'), + bytes32('branch11'), + bytes32('branch12'), + bytes32('branch13'), + bytes32('branch14'), + bytes32('branch15'), + bytes32('branch16'), + bytes32('branch17'), + bytes32('branch18'), + bytes32('branch19'), + bytes32('branch20'), + bytes32('branch21'), + bytes32('branch22'), + bytes32('branch23'), + bytes32('branch24'), + bytes32('branch25'), + bytes32('branch26'), + bytes32('branch27'), + bytes32('branch28'), + bytes32('branch29'), + bytes32('branch30'), + bytes32('branch31'), + bytes32('branch32') + ]; + uint256 internal _treeCount = 1; + bytes internal _treeData = abi.encode(_treeBranches, _treeCount); + bytes32[] internal _leavesToInsert = [bytes32('leave1'), bytes32('leave2')]; + + // Events + event ResponseDisputed( + bytes32 indexed _requestId, + 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'); + + treeVerifier = ITreeVerifier(makeAddr('TreeVerifier')); + vm.etch(address(treeVerifier), hex'069420'); + + rootVerificationModule = new RootVerificationModule(oracle); + } +} + +contract RootVerificationModule_Unit_ModuleData is BaseTest { + /** + * @notice Test that the moduleName function returns the correct name + */ + function test_moduleNameReturnsName() public { + assertEq(rootVerificationModule.moduleName(), 'RootVerificationModule'); + } + + /** + * @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( + IRootVerificationModule.RequestParameters({ + treeData: _treeData, + leavesToInsert: _leavesToInsert, + treeVerifier: treeVerifier, + accountingExtension: IAccountingExtension(_accountingExtension), + bondToken: IERC20(_randomToken), + bondSize: _bondSize + }) + ); + + IRootVerificationModule.RequestParameters memory _params = rootVerificationModule.decodeRequestData(_requestData); + + bytes32[32] memory _treeBranchesStored; + uint256 _treeCountStored; + (_treeBranchesStored, _treeCountStored) = abi.decode(_params.treeData, (bytes32[32], uint256)); + + // Check: is the request data properly stored? + for (uint256 _i = 0; _i < _treeBranches.length; _i++) { + assertEq(_treeBranchesStored[_i], _treeBranches[_i], 'Mismatch: decoded tree branch'); + } + for (uint256 _i = 0; _i < _leavesToInsert.length; _i++) { + assertEq(_params.leavesToInsert[_i], _leavesToInsert[_i], 'Mismatch: decoded leave to insert'); + } + assertEq(_treeCountStored, _treeCount, 'Mismatch: decoded tree count'); + assertEq(address(_params.treeVerifier), address(treeVerifier), 'Mismatch: decoded tree 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'); + } +} + +contract RootVerificationModule_Unit_DisputeResponse is BaseTest { + /** + * @notice Test if dispute incorrect response returns the correct status + */ + function test_disputeIncorrectResponse( + bytes memory _treeData, + bytes32[] calldata _leavesToInsert, + IAccountingExtension _accountingExtension, + uint256 _bondSize + ) public { + mockRequest.disputeModuleData = abi.encode( + IRootVerificationModule.RequestParameters({ + treeData: _treeData, + leavesToInsert: _leavesToInsert, + treeVerifier: treeVerifier, + accountingExtension: _accountingExtension, + bondToken: _token, + bondSize: _bondSize + }) + ); + + // Create new Response memory struct with random values + mockResponse.response = abi.encode(bytes32('randomRoot')); + + // Mock and expect the call to the tree verifier, calculating the root + _mockAndExpect( + address(treeVerifier), + abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), + abi.encode(bytes32('randomRoot2')) + ); + + // Mock and expect the call the oracle, updating the dispute's status + _mockAndExpect( + address(oracle), + abi.encodeWithSelector( + oracle.updateDisputeStatus.selector, mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Won + ), + abi.encode(true) + ); + + vm.prank(address(oracle)); + rootVerificationModule.disputeResponse(mockRequest, mockResponse, mockDispute); + } + + function test_emitsEvent( + bytes memory _treeData, + bytes32[] calldata _leavesToInsert, + IAccountingExtension _accountingExtension, + uint256 _bondSize + ) public { + mockRequest.disputeModuleData = abi.encode( + IRootVerificationModule.RequestParameters({ + treeData: _treeData, + leavesToInsert: _leavesToInsert, + treeVerifier: treeVerifier, + accountingExtension: _accountingExtension, + bondToken: _token, + bondSize: _bondSize + }) + ); + + // Create new Response memory struct with random values + mockResponse.response = abi.encode(bytes32('randomRoot')); + mockResponse.requestId = _getId(mockRequest); + mockDispute.requestId = _getId(mockRequest); + mockDispute.responseId = _getId(mockResponse); + + // Mock and expect the call to the tree verifier, calculating the root + _mockAndExpect( + address(treeVerifier), + abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), + abi.encode(bytes32('randomRoot2')) + ); + + // Mock and expect the call the oracle, updating the dispute's status + _mockAndExpect( + address(oracle), + abi.encodeWithSelector( + oracle.updateDisputeStatus.selector, mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Won + ), + abi.encode(true) + ); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(rootVerificationModule)); + emit ResponseDisputed({ + _requestId: mockDispute.requestId, + _responseId: mockDispute.responseId, + _disputeId: _getId(mockDispute), + _dispute: mockDispute, + _blockNumber: block.number + }); + + vm.prank(address(oracle)); + rootVerificationModule.disputeResponse(mockRequest, mockResponse, mockDispute); + } + + /** + * @notice Test if dispute correct response returns the correct status + */ + function test_disputeCorrectResponse( + bytes memory _treeData, + bytes32[] calldata _leavesToInsert, + IAccountingExtension _accountingExtension, + uint256 _bondSize + ) public { + mockRequest.disputeModuleData = abi.encode( + IRootVerificationModule.RequestParameters({ + treeData: _treeData, + leavesToInsert: _leavesToInsert, + treeVerifier: treeVerifier, + accountingExtension: _accountingExtension, + bondToken: _token, + bondSize: _bondSize + }) + ); + + bytes memory _encodedCorrectRoot = abi.encode(bytes32('randomRoot')); + + // Create new Response memory struct with random values + mockResponse.response = _encodedCorrectRoot; + mockResponse.requestId = _getId(mockRequest); + + // Mock and expect the call to the tree verifier, calculating the root + _mockAndExpect( + address(treeVerifier), + abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), + _encodedCorrectRoot + ); + + // Mock and expect the call the oracle, updating the dispute's status + _mockAndExpect( + address(oracle), + abi.encodeWithSelector( + oracle.updateDisputeStatus.selector, mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Lost + ), + abi.encode(true) + ); + + vm.prank(address(oracle)); + rootVerificationModule.disputeResponse(mockRequest, mockResponse, mockDispute); + } + + /** + * @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: revert if not called by the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(_randomCaller); + rootVerificationModule.disputeResponse(mockRequest, mockResponse, mockDispute); + } +}