diff --git a/docs/src/content/modules/dispute/circuit_resolver_module.md b/docs/src/content/modules/dispute/circuit_resolver_module.md index 4806899e..81ac72f4 100644 --- a/docs/src/content/modules/dispute/circuit_resolver_module.md +++ b/docs/src/content/modules/dispute/circuit_resolver_module.md @@ -10,10 +10,9 @@ 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. -- `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. +- `decodeRequestData`: Returns the decoded data for a request. +- `disputeResponse`: 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`: Updates the status of the dispute and resolves it by proposing the correct circuit as a response and finalizing the request. ### Request Parameters 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 diff --git a/docs/src/content/modules/finality/callback_module.md b/docs/src/content/modules/finality/callback_module.md index e2c1ce9c..78409c9a 100644 --- a/docs/src/content/modules/finality/callback_module.md +++ b/docs/src/content/modules/finality/callback_module.md @@ -10,8 +10,8 @@ The Callback Module is a finality module that allows users to call a function on ### Key Methods -- `decodeRequestData(bytes32 _requestId)`: Returns the decoded data for a request. -- `finalizeRequest(bytes32 _requestId, address)`: Executing the callback call on the target. +- `decodeRequestData`: Returns the decoded data for a request. +- `finalizeRequest`: Executing the callback call on the target. ### Request Parameters @@ -25,4 +25,3 @@ As any finality module, the `CallbackModule` implements the `finalizeRequest` fu ## 4. Gotchas - The success of the callback call in `finalizeRequest` is purposely not checked, specifying a function or parameters that lead to a revert will not stop the request from being finalized. -- The target must be a contract. diff --git a/docs/src/content/modules/response/bonded_response_module.md b/docs/src/content/modules/response/bonded_response_module.md index b8c98800..4aaa6c67 100644 --- a/docs/src/content/modules/response/bonded_response_module.md +++ b/docs/src/content/modules/response/bonded_response_module.md @@ -10,10 +10,9 @@ The Bonded Response Module is a contract that allows users to propose a response ### Key Methods -- `decodeRequestData(bytes32 _requestId)`: Returns the decoded data for a request. -- `propose(bytes32 _requestId, address _proposer, bytes calldata _responseData)`: Proposes a response for a request, bonding the proposer's tokens. -- `deleteResponse(bytes32 _requestId, bytes32 _responseId, address _proposer)`: Allows a user to delete an undisputed response they proposed before the deadline, releasing the bond. -- `finalizeRequest(bytes32 _requestId, address _finalizer)`: Finalizes the request. +- `decodeRequestData`: Returns the decoded data for a request. +- `propose`: Proposes a response for a request, bonding the proposer's tokens. +- `finalizeRequest`: Finalizes the request. ### Request Parameters @@ -24,14 +23,12 @@ The Bonded Response Module is a contract that allows users to propose a response ## 3. Key Mechanisms & Concepts -- Deleting a response: If a proposer realizes the response they've submitted is incorrect, they can delete it. Note that disputed responses cannot be taken back. - - Early finalization: It is possible for pre-dispute modules to atomically calculate the correct response on-chain, decide on the result of a dispute and finalize the request before its deadline. +- Dispute window: Prevents proposers from submitting a response 1 block before the deadline and finalizing it in the next block, leaving disputers no time to dispute the response. + ## 4. Gotchas - In case of no valid responses, a request can be finalized after the deadline and the requester will get back their tokens. -- A proposer might submit a response 1 block before the deadline and finalize it in the next block, making it impossible to dispute. - Users cannot propose a response after the deadline for a request. - Users cannot propose a response if an undisputed response has already been proposed. -- Users cannot delete a response after the proposing deadline. diff --git a/package.json b/package.json index e85440a1..d3eac0c9 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-48b0248d", + "@defi-wonderland/prophet-core-contracts": "0.0.0-1ae08a81", "@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/solidity/contracts/modules/dispute/CircuitResolverModule.sol b/solidity/contracts/modules/dispute/CircuitResolverModule.sol index 015d8a76..8518a2b4 100644 --- a/solidity/contracts/modules/dispute/CircuitResolverModule.sol +++ b/solidity/contracts/modules/dispute/CircuitResolverModule.sol @@ -1,91 +1,93 @@ -// // 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 instead of reading from `_correctResponses` + 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}); + + 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 _correctResponses[_dispute.requestId]; + } + + /// @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); + + if (!_success) revert CircuitResolverModule_VerificationFailed(); + + _correctResponses[_response.requestId] = _correctResponse; + + IOracle.DisputeStatus _status = _response.response.length != _correctResponse.length + || keccak256(_response.response) != keccak256(_correctResponse) + ? IOracle.DisputeStatus.Won + : IOracle.DisputeStatus.Lost; + + 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/contracts/modules/dispute/RootVerificationModule.sol b/solidity/contracts/modules/dispute/RootVerificationModule.sol index e19f1cf0..e954fc1a 100644 --- a/solidity/contracts/modules/dispute/RootVerificationModule.sol +++ b/solidity/contracts/modules/dispute/RootVerificationModule.sol @@ -1,94 +1,95 @@ -// // 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 {IRootVerificationModule} from '../../../interfaces/modules/dispute/IRootVerificationModule.sol'; -// import {MerkleLib} from '../../libraries/MerkleLib.sol'; - -// contract RootVerificationModule is Module, IRootVerificationModule { -// using MerkleLib for MerkleLib.Tree; - -// /** -// * @notice The calculated correct root for a given request -// */ -// mapping(bytes32 _requestId => bytes32 _correctRoot) internal _correctRoots; - -// constructor(IOracle _oracle) Module(_oracle) {} - -// /// @inheritdoc IModule -// function moduleName() external pure returns (string memory _moduleName) { -// return 'RootVerificationModule'; -// } - -// /// @inheritdoc IRootVerificationModule -// function decodeRequestData(bytes32 _requestId) public view returns (RequestParameters memory _params) { -// _params = abi.decode(requestData[_requestId], (RequestParameters)); -// } - -// /// @inheritdoc IRootVerificationModule -// function disputeEscalated(bytes32 _disputeId) external onlyOracle {} - -// /// @inheritdoc IRootVerificationModule -// function onDisputeStatusChange(bytes32, IOracle.Dispute memory _dispute) external onlyOracle { -// RequestParameters memory _params = decodeRequestData(_dispute.requestId); - -// IOracle.Response memory _response = ORACLE.getResponse(_dispute.responseId); - -// bool _won = abi.decode(_response.response, (bytes32)) != _correctRoots[_dispute.requestId]; - -// 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(_correctRoots[_dispute.requestId])); -// ORACLE.finalize(_dispute.requestId, _correctResponseId); -// } else { -// ORACLE.finalize(_dispute.requestId, _dispute.responseId); -// } - -// delete _correctRoots[_dispute.requestId]; - -// emit DisputeStatusChanged({ -// _requestId: _dispute.requestId, -// _responseId: _dispute.responseId, -// _disputer: _dispute.disputer, -// _proposer: _dispute.proposer, -// _status: _dispute.status -// }); -// } - -// /// @inheritdoc IRootVerificationModule -// 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); - -// bytes32 _correctRoot = _params.treeVerifier.calculateRoot(_params.treeData, _params.leavesToInsert); -// _correctRoots[_requestId] = _correctRoot; - -// bool _won = abi.decode(_response.response, (bytes32)) != _correctRoot; - -// _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 {IRootVerificationModule} from '../../../interfaces/modules/dispute/IRootVerificationModule.sol'; +import {MerkleLib} from '../../libraries/MerkleLib.sol'; + +contract RootVerificationModule is Module, IRootVerificationModule { + using MerkleLib for MerkleLib.Tree; + + /** + * @notice The calculated correct root for a given request + */ + mapping(bytes32 _requestId => bytes32 _correctRoot) internal _correctRoots; + + constructor(IOracle _oracle) Module(_oracle) {} + + /// @inheritdoc IModule + function moduleName() external pure returns (string memory _moduleName) { + return 'RootVerificationModule'; + } + + /// @inheritdoc IRootVerificationModule + function decodeRequestData(bytes calldata _data) public pure returns (RequestParameters memory _params) { + _params = abi.decode(_data, (RequestParameters)); + } + + /// @inheritdoc IRootVerificationModule + 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); + + bytes32 _correctRoot = _correctRoots[_dispute.requestId]; + bool _won = abi.decode(_response.response, (bytes32)) != _correctRoot; + + 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: abi.encode(_correctRoot) + }); + + 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]; + } + + /// @inheritdoc IRootVerificationModule + function disputeResponse( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external onlyOracle { + RequestParameters memory _params = decodeRequestData(_request.disputeModuleData); + + bytes32 _correctRoot = _params.treeVerifier.calculateRoot(_params.treeData, _params.leavesToInsert); + _correctRoots[_response.requestId] = _correctRoot; + + IOracle.DisputeStatus _status = + abi.decode(_response.response, (bytes32)) != _correctRoot ? IOracle.DisputeStatus.Won : IOracle.DisputeStatus.Lost; + + 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/contracts/modules/finality/CallbackModule.sol b/solidity/contracts/modules/finality/CallbackModule.sol index 3f3faf9d..f3eea3d9 100644 --- a/solidity/contracts/modules/finality/CallbackModule.sol +++ b/solidity/contracts/modules/finality/CallbackModule.sol @@ -1,42 +1,34 @@ -// // 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 {ICallbackModule} from '../../../interfaces/modules/finality/ICallbackModule.sol'; +import {ICallbackModule} from '../../../interfaces/modules/finality/ICallbackModule.sol'; -// contract CallbackModule is Module, ICallbackModule { -// constructor(IOracle _oracle) Module(_oracle) {} +contract CallbackModule is Module, ICallbackModule { + constructor(IOracle _oracle) Module(_oracle) {} -// /// @inheritdoc IModule -// function moduleName() public pure returns (string memory _moduleName) { -// _moduleName = 'CallbackModule'; -// } + /// @inheritdoc IModule + function moduleName() public pure returns (string memory _moduleName) { + _moduleName = 'CallbackModule'; + } -// /// @inheritdoc ICallbackModule -// function decodeRequestData(bytes32 _requestId) public view returns (RequestParameters memory _params) { -// _params = abi.decode(requestData[_requestId], (RequestParameters)); -// } + /// @inheritdoc ICallbackModule + function decodeRequestData(bytes calldata _data) public pure returns (RequestParameters memory _params) { + _params = abi.decode(_data, (RequestParameters)); + } -// /** -// * @notice Checks if the target address has code (i.e. is a contract) -// * @param _data The encoded data for the request -// */ -// function _afterSetupRequest(bytes32, bytes calldata _data) internal view override { -// RequestParameters memory _params = abi.decode(_data, (RequestParameters)); -// if (_params.target.code.length == 0) revert CallbackModule_TargetHasNoCode(); -// } - -// /// @inheritdoc ICallbackModule -// function finalizeRequest( -// bytes32 _requestId, -// address _finalizer -// ) external override(Module, ICallbackModule) onlyOracle { -// RequestParameters memory _params = decodeRequestData(_requestId); -// _params.target.call(_params.data); -// emit Callback(_requestId, _params.target, _params.data); -// emit RequestFinalized(_requestId, _finalizer); -// } -// } + /// @inheritdoc ICallbackModule + function finalizeRequest( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + address _finalizer + ) external override(Module, ICallbackModule) onlyOracle { + RequestParameters memory _params = decodeRequestData(_request.finalityModuleData); + _params.target.call(_params.data); + emit Callback(_response.requestId, _params.target, _params.data); + emit RequestFinalized(_response.requestId, _response, _finalizer); + } +} diff --git a/solidity/contracts/modules/response/BondedResponseModule.sol b/solidity/contracts/modules/response/BondedResponseModule.sol index 3dffd2fb..c52bf5df 100644 --- a/solidity/contracts/modules/response/BondedResponseModule.sol +++ b/solidity/contracts/modules/response/BondedResponseModule.sol @@ -1,118 +1,91 @@ -// // 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 {IBondedResponseModule} from '../../../interfaces/modules/response/IBondedResponseModule.sol'; - -// contract BondedResponseModule is Module, IBondedResponseModule { -// constructor(IOracle _oracle) Module(_oracle) {} - -// /// @inheritdoc IModule -// function moduleName() public pure returns (string memory _moduleName) { -// _moduleName = 'BondedResponseModule'; -// } - -// /// @inheritdoc IBondedResponseModule -// function decodeRequestData(bytes32 _requestId) public view returns (RequestParameters memory _params) { -// _params = abi.decode(requestData[_requestId], (RequestParameters)); -// } - -// /// @inheritdoc IBondedResponseModule -// function propose( -// bytes32 _requestId, -// address _proposer, -// bytes calldata _responseData, -// address _sender -// ) external onlyOracle returns (IOracle.Response memory _response) { -// RequestParameters memory _params = decodeRequestData(_requestId); - -// // Cannot propose after the deadline -// if (block.timestamp >= _params.deadline) revert BondedResponseModule_TooLateToPropose(); - -// // Cannot propose to a request with a response, unless the response is being disputed -// bytes32[] memory _responseIds = ORACLE.getResponseIds(_requestId); -// uint256 _responsesLength = _responseIds.length; - -// if (_responsesLength != 0) { -// bytes32 _disputeId = ORACLE.getResponse(_responseIds[_responsesLength - 1]).disputeId; - -// // Allowing one undisputed response at a time -// if (_disputeId == bytes32(0)) revert BondedResponseModule_AlreadyResponded(); -// IOracle.Dispute memory _dispute = ORACLE.getDispute(_disputeId); -// // TODO: leaving a note here to re-check this check if a new status is added -// // If the dispute was lost, we assume the proposed answer was correct. DisputeStatus.None should not be reachable due to the previous check. -// if (_dispute.status == IOracle.DisputeStatus.Lost) revert BondedResponseModule_AlreadyResponded(); -// } - -// _response = IOracle.Response({ -// requestId: _requestId, -// disputeId: bytes32(0), -// proposer: _proposer, -// response: _responseData, -// createdAt: block.timestamp -// }); - -// _params.accountingExtension.bond({ -// _bonder: _response.proposer, -// _requestId: _requestId, -// _token: _params.bondToken, -// _amount: _params.bondSize, -// _sender: _sender -// }); - -// emit ProposeResponse(_requestId, _proposer, _responseData); -// } - -// /// @inheritdoc IBondedResponseModule -// function deleteResponse(bytes32 _requestId, bytes32, address _proposer) external onlyOracle { -// RequestParameters memory _params = decodeRequestData(_requestId); - -// if (block.timestamp > _params.deadline) revert BondedResponseModule_TooLateToDelete(); - -// _params.accountingExtension.release({ -// _bonder: _proposer, -// _requestId: _requestId, -// _token: _params.bondToken, -// _amount: _params.bondSize -// }); -// } - -// /// @inheritdoc IBondedResponseModule -// function finalizeRequest( -// bytes32 _requestId, -// address _finalizer -// ) external override(IBondedResponseModule, Module) onlyOracle { -// RequestParameters memory _params = decodeRequestData(_requestId); - -// bool _isModule = ORACLE.allowedModule(_requestId, _finalizer); - -// if (!_isModule && block.timestamp < _params.deadline) { -// revert BondedResponseModule_TooEarlyToFinalize(); -// } - -// IOracle.Response memory _response = ORACLE.getFinalizedResponse(_requestId); -// if (_response.createdAt != 0) { -// if (!_isModule && block.timestamp < _response.createdAt + _params.disputeWindow) { -// revert BondedResponseModule_TooEarlyToFinalize(); -// } - -// _params.accountingExtension.release({ -// _bonder: _response.proposer, -// _requestId: _requestId, -// _token: _params.bondToken, -// _amount: _params.bondSize -// }); -// } -// emit RequestFinalized(_requestId, _finalizer); -// } - -// /// @inheritdoc Module -// function _afterSetupRequest(bytes32, bytes calldata _data) internal view override { -// RequestParameters memory _params = abi.decode(_data, (RequestParameters)); -// if (_params.deadline <= block.timestamp) { -// revert BondedResponseModule_InvalidRequest(); -// } -// } -// } +// 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 {IBondedResponseModule} from '../../../interfaces/modules/response/IBondedResponseModule.sol'; + +contract BondedResponseModule is Module, IBondedResponseModule { + constructor(IOracle _oracle) Module(_oracle) {} + + /// @inheritdoc IModule + function moduleName() public pure returns (string memory _moduleName) { + _moduleName = 'BondedResponseModule'; + } + + /// @inheritdoc IBondedResponseModule + function decodeRequestData(bytes calldata _data) public pure returns (RequestParameters memory _params) { + _params = abi.decode(_data, (RequestParameters)); + } + + /// @inheritdoc IBondedResponseModule + function propose( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + address _sender + ) external onlyOracle { + RequestParameters memory _params = decodeRequestData(_request.responseModuleData); + + // Cannot propose after the deadline + if (block.timestamp >= _params.deadline) revert BondedResponseModule_TooLateToPropose(); + + // Cannot propose to a request with a response, unless the response is being disputed + bytes32[] memory _responseIds = ORACLE.getResponseIds(_response.requestId); + uint256 _responsesLength = _responseIds.length; + + if (_responsesLength != 0) { + bytes32 _disputeId = ORACLE.disputeOf(_responseIds[_responsesLength - 1]); + + // Allowing one undisputed response at a time + if (_disputeId == bytes32(0)) revert BondedResponseModule_AlreadyResponded(); + IOracle.DisputeStatus _status = ORACLE.disputeStatus(_disputeId); + // TODO: leaving a note here to re-check this check if a new status is added + // If the dispute was lost, we assume the proposed answer was correct. DisputeStatus.None should not be reachable due to the previous check. + if (_status == IOracle.DisputeStatus.Lost) revert BondedResponseModule_AlreadyResponded(); + } + + _params.accountingExtension.bond({ + _bonder: _response.proposer, + _requestId: _response.requestId, + _token: _params.bondToken, + _amount: _params.bondSize, + _sender: _sender + }); + + emit ResponseProposed(_response.requestId, _response, block.number); + } + + /// @inheritdoc IBondedResponseModule + function finalizeRequest( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + address _finalizer + ) external override(IBondedResponseModule, Module) onlyOracle { + RequestParameters memory _params = decodeRequestData(_request.responseModuleData); + + // TODO: If deadline has passed, we can skip the caller validation + bool _isModule = ORACLE.allowedModule(_response.requestId, _finalizer); + + if (!_isModule && block.timestamp < _params.deadline) { + revert BondedResponseModule_TooEarlyToFinalize(); + } + + uint256 _responseCreatedAt = ORACLE.createdAt(_getId(_response)); + + if (_responseCreatedAt != 0) { + if (!_isModule && block.timestamp < _responseCreatedAt + _params.disputeWindow) { + revert BondedResponseModule_TooEarlyToFinalize(); + } + + _params.accountingExtension.release({ + _bonder: _response.proposer, + _requestId: _response.requestId, + _token: _params.bondToken, + _amount: _params.bondSize + }); + } + + emit RequestFinalized(_response.requestId, _response, _finalizer); + } +} diff --git a/solidity/interfaces/modules/dispute/ICircuitResolverModule.sol b/solidity/interfaces/modules/dispute/ICircuitResolverModule.sol index fc224ce0..ae3272fd 100644 --- a/solidity/interfaces/modules/dispute/ICircuitResolverModule.sol +++ b/solidity/interfaces/modules/dispute/ICircuitResolverModule.sol @@ -1,68 +1,94 @@ -// // 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 -// //////////////////////////////////////////////////////////////*/ + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ -// /** -// * @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 Thrown when the verification of a response fails + */ + error CircuitResolverModule_VerificationFailed(); -// /// @inheritdoc IDisputeModule -// function disputeResponse( -// bytes32 _requestId, -// bytes32 _responseId, -// address _disputer, -// address _proposer -// ) external returns (IOracle.Dispute memory _dispute); + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ -// /// @inheritdoc IDisputeModule -// function onDisputeStatusChange(bytes32 _disputeId, IOracle.Dispute memory _dispute) external; + /** + * @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 disputeEscalated(bytes32 _disputeId) external; -// } + /** + * @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; + + /** + * @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 + * @param _response The response that was disputed + * @param _dispute The dispute + */ + function onDisputeStatusChange( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external; +} diff --git a/solidity/interfaces/modules/dispute/IRootVerificationModule.sol b/solidity/interfaces/modules/dispute/IRootVerificationModule.sol index 682468eb..f29c92f1 100644 --- a/solidity/interfaces/modules/dispute/IRootVerificationModule.sol +++ b/solidity/interfaces/modules/dispute/IRootVerificationModule.sol @@ -1,84 +1,81 @@ -// // 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 {ITreeVerifier} from '../../ITreeVerifier.sol'; -// import {IAccountingExtension} from '../../extensions/IAccountingExtension.sol'; +import {ITreeVerifier} from '../../ITreeVerifier.sol'; +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. -// */ -// interface IRootVerificationModule is IDisputeModule { -// /*/////////////////////////////////////////////////////////////// -// STRUCTS -// //////////////////////////////////////////////////////////////*/ +/* + * @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. + */ +interface IRootVerificationModule is IDisputeModule { + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Parameters of the request as stored in the module -// * @param treeData The data of the tree -// * @param leavesToInsert The leaves to insert in the tree -// * @param treeVerifier The tree verifier to use to calculate the correct root -// * @param accountingExtension The accounting extension to use for bonds and payments -// * @param bondToken The token to use for bonds and payments -// * @param bondSize The size of the bond to participate in the request -// */ -// struct RequestParameters { -// bytes treeData; -// bytes32[] leavesToInsert; -// ITreeVerifier treeVerifier; -// IAccountingExtension accountingExtension; -// IERC20 bondToken; -// uint256 bondSize; -// } -// /*/////////////////////////////////////////////////////////////// -// LOGIC -// //////////////////////////////////////////////////////////////*/ + /** + * @notice Parameters of the request as stored in the module + * @param treeData The data of the tree + * @param leavesToInsert The leaves to insert in the tree + * @param treeVerifier The tree verifier to use to calculate the correct root + * @param accountingExtension The accounting extension to use for bonds and payments + * @param bondToken The token to use for bonds and payments + * @param bondSize The size of the bond to participate in the request + */ + struct RequestParameters { + bytes treeData; + bytes32[] leavesToInsert; + ITreeVerifier treeVerifier; + IAccountingExtension accountingExtension; + IERC20 bondToken; + uint256 bondSize; + } + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Returns the decoded data for a request -// * @param _requestId The ID of the request -// * @return _params The struct containing the parameters for the request -// */ -// function decodeRequestData(bytes32 _requestId) external view returns (RequestParameters memory _params); + /** + * @notice Returns the decoded data for a request + * @param _data The encoded request parameters + * @return _params The decoded parameters of the request + */ + function decodeRequestData(bytes calldata _data) external view returns (RequestParameters memory _params); -// /** -// * @notice Calculates the correct root and compares it to the proposed one. -// * @dev Since this is a pre-dispute module, the dispute status is updated after checking -// * if the disputed response is indeed wrong, since it is calculated on dispute. -// * @param _requestId The id of the request from which the response is being disputed -// * @param _responseId The id of the response being disputed -// * @param _disputer The user who is disputing the response -// * @param _proposer The proposer of the response being disputed -// * @return _dispute The dispute of the current response with the updated status -// */ -// function disputeResponse( -// bytes32 _requestId, -// bytes32 _responseId, -// address _disputer, -// address _proposer -// ) external returns (IOracle.Dispute memory _dispute); + /** + * @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; -// /** -// * @notice Updates the status of the dispute and resolves it by proposing the correct root -// * as a response and finalizing the request. -// * @dev The correct root is retrieved from storage and compared to the proposed root. -// * If the dispute is won, the disputer is paid. In both cases, the request is finalized. -// * @param _dispute The dispute of the current response -// */ -// function onDisputeStatusChange(bytes32, IOracle.Dispute memory _dispute) external; - -// /** -// * @dev This function is present to comply with the module interface but it -// * is not implemented since this is a pre-dispute module. -// */ -// function disputeEscalated(bytes32 _disputeId) external; -// } + /** + * @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, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external; +} diff --git a/solidity/interfaces/modules/finality/ICallbackModule.sol b/solidity/interfaces/modules/finality/ICallbackModule.sol index 99b298ac..d5cb064f 100644 --- a/solidity/interfaces/modules/finality/ICallbackModule.sol +++ b/solidity/interfaces/modules/finality/ICallbackModule.sol @@ -1,65 +1,63 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; - -// import {IFinalityModule} from -// '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/finality/IFinalityModule.sol'; - -// /** -// * @title CallbackModule -// * @notice Module allowing users to call a function on a contract -// * as a result of a request being finalized. -// */ -// interface ICallbackModule is IFinalityModule { -// /*/////////////////////////////////////////////////////////////// -// EVENTS -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice A callback has been executed -// * @param _requestId The id of the request being finalized -// * @param _target The target address for the callback -// * @param _data The calldata forwarded to the _target -// */ -// event Callback(bytes32 indexed _requestId, address indexed _target, bytes _data); - -// /*/////////////////////////////////////////////////////////////// -// ERRORS -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice Thrown when the target address has no code (i.e. is not a contract) -// */ -// error CallbackModule_TargetHasNoCode(); - -// /*/////////////////////////////////////////////////////////////// -// STRUCTS -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice Parameters of the request as stored in the module -// * @param target The target address for the callback -// * @param data The calldata forwarded to the _target -// */ -// struct RequestParameters { -// address target; -// bytes data; -// } - -// /*/////////////////////////////////////////////////////////////// -// LOGIC -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice Returns the decoded data for a request -// * @param _requestId The ID of the request -// * @return _params The struct containing the parameters for the request -// */ -// function decodeRequestData(bytes32 _requestId) external view returns (RequestParameters memory _params); - -// /** -// * @notice Finalizes the request by executing the callback call on the target -// * @dev The success of the callback call is purposely not checked -// * @param _requestId The id of the request -// */ -// function finalizeRequest(bytes32 _requestId, address) external; -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; +import {IFinalityModule} from + '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/finality/IFinalityModule.sol'; + +/** + * @title CallbackModule + * @notice Module allowing users to call a function on a contract + * as a result of a request being finalized. + */ +interface ICallbackModule is IFinalityModule { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice A callback has been executed + * @param _requestId The id of the request being finalized + * @param _target The target address for the callback + * @param _data The calldata forwarded to the _target + */ + event Callback(bytes32 indexed _requestId, address indexed _target, bytes _data); + + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Parameters of the request as stored in the module + * @param target The target address for the callback + * @param data The calldata forwarded to the _target + */ + struct RequestParameters { + address target; + bytes data; + } + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @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 the request by executing the callback call on the target + * @dev The success of the callback call is purposely not checked + * @param _request The request being finalized + * @param _response The final response + * @param _finalizer The address that initiated the finalization + */ + function finalizeRequest( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + address _finalizer + ) external; +} diff --git a/solidity/interfaces/modules/response/IBondedResponseModule.sol b/solidity/interfaces/modules/response/IBondedResponseModule.sol index c159b988..1b460221 100644 --- a/solidity/interfaces/modules/response/IBondedResponseModule.sol +++ b/solidity/interfaces/modules/response/IBondedResponseModule.sol @@ -1,117 +1,102 @@ -// // 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 {IResponseModule} from -// '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/response/IResponseModule.sol'; +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; +import {IResponseModule} from + '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/response/IResponseModule.sol'; -// import {IAccountingExtension} from '../../extensions/IAccountingExtension.sol'; +import {IAccountingExtension} from '../../extensions/IAccountingExtension.sol'; -// /* -// * @title BondedResponseModule -// * @notice Module allowing users to propose a response for a request -// * by bonding tokens. -// */ -// interface IBondedResponseModule is IResponseModule { -// /*/////////////////////////////////////////////////////////////// -// ERRORS -// //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Emitted when a response is proposed -// * @param _requestId The ID of the request that the response was proposed -// * @param _proposer The user that proposed the response -// * @param _responseData The data for the response -// */ -// event ProposeResponse(bytes32 indexed _requestId, address _proposer, bytes _responseData); -// /*/////////////////////////////////////////////////////////////// -// ERRORS -// //////////////////////////////////////////////////////////////*/ +/* + * @title BondedResponseModule + * @notice Module allowing users to propose a response for a request by bonding tokens + */ +interface IBondedResponseModule is IResponseModule { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + /** + * @notice Emitted when a response is proposed + * + * @param _requestId The ID of the request that the response was proposed + * @param _response The proposed response + * @param _blockNumber The number of the block in which the response was proposed + */ + event ResponseProposed(bytes32 indexed _requestId, IOracle.Response _response, uint256 indexed _blockNumber); -// /** -// * @notice Thrown when trying to finalize a request before the deadline -// */ -// error BondedResponseModule_TooEarlyToFinalize(); + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Thrown when trying to propose a response after deadline -// */ -// error BondedResponseModule_TooLateToPropose(); + /** + * @notice Thrown when trying to finalize a request before the deadline + */ + error BondedResponseModule_TooEarlyToFinalize(); -// /** -// * @notice Thrown when trying to propose a response while an undisputed response is already proposed -// */ -// error BondedResponseModule_AlreadyResponded(); + /** + * @notice Thrown when trying to propose a response after deadline + */ + error BondedResponseModule_TooLateToPropose(); -// /** -// * @notice Thrown when trying to delete a response after the proposing deadline -// */ -// error BondedResponseModule_TooLateToDelete(); + /** + * @notice Thrown when trying to propose a response while an undisputed response is already proposed + */ + error BondedResponseModule_AlreadyResponded(); -// /** -// * @notice Thrown when trying to create an invalid request -// */ -// error BondedResponseModule_InvalidRequest(); + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ -// /*/////////////////////////////////////////////////////////////// -// STRUCTS -// //////////////////////////////////////////////////////////////*/ + /** + * @notice Parameters of the request as stored in the module + * + * @param accountingExtension The accounting extension used to bond and release tokens + * @param bondToken The token used for bonds in the request + * @param bondSize The amount of `_bondToken` to bond to propose a response and dispute + * @param deadline The timestamp after which no responses can be proposed + * @param disputeWindow The time buffer required to finalize a request + */ + struct RequestParameters { + IAccountingExtension accountingExtension; + IERC20 bondToken; + uint256 bondSize; + uint256 deadline; + uint256 disputeWindow; + } -// /** -// * @notice Parameters of the request as stored in the module -// * @param accountingExtension The accounting extension used to bond and release tokens -// * @param bondToken The token used for bonds in the request -// * @param bondSize The amount of `_bondToken` to bond to propose a response and dispute -// * @param deadline The timestamp after which no responses can be proposed -// * @param disputeWindow The time buffer required to finalize a request -// */ -// struct RequestParameters { -// IAccountingExtension accountingExtension; -// IERC20 bondToken; -// uint256 bondSize; -// uint256 deadline; -// uint256 disputeWindow; -// } + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ -// /*/////////////////////////////////////////////////////////////// -// LOGIC -// //////////////////////////////////////////////////////////////*/ + /** + * @notice Returns the decoded data for a request + * + * @param _data The encoded data + * @return _params The struct containing the parameters for the request + */ + function decodeRequestData(bytes calldata _data) external pure returns (RequestParameters memory _params); -// /** -// * @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 Proposes a response for a request, bonding the proposer's tokens + * + * @dev The user must have previously deposited tokens into the accounting extension + * @param _request The request to propose a response to + * @param _response The response being proposed + * @param _sender The address that initiated the transaction + */ + function propose(IOracle.Request calldata _request, IOracle.Response calldata _response, address _sender) external; -// /** -// * @notice Proposes a response for a request, bonding the proposer's tokens -// * @dev The user must have previously deposited tokens into the accounting extension -// * @param _requestId The ID of the request to propose a response for -// * @param _proposer The user proposing the response -// * @param _responseData The data for the response -// * @param _sender The address calling propose on the Oracle -// * @return _response The struct of proposed response -// */ -// function propose( -// bytes32 _requestId, -// address _proposer, -// bytes calldata _responseData, -// address _sender -// ) external returns (IOracle.Response memory _response); - -// /** -// * @notice Allows a user to delete an undisputed response they proposed before the deadline, releasing the bond -// * @param _requestId The ID of the request to delete the response from -// * @param _responseId The ID of the response to delete -// * @param _proposer The user who proposed the response -// */ -// function deleteResponse(bytes32 _requestId, bytes32 _responseId, address _proposer) external; - -// /** -// * @notice Finalizes the request by releasing the bond of the proposer -// * @param _requestId The ID of the request to finalize -// * @param _finalizer The user who triggered the finalization -// */ -// function finalizeRequest(bytes32 _requestId, address _finalizer) external; -// } + /** + * @notice Finalizes the request by releasing the bond of the proposer + * + * @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/integration/ResponseProposal.t.sol b/solidity/test/integration/ResponseProposal.t.sol index d7809076..a17486d7 100644 --- a/solidity/test/integration/ResponseProposal.t.sol +++ b/solidity/test/integration/ResponseProposal.t.sol @@ -149,40 +149,4 @@ // assertEq(_deletedResponse.createdAt, 0); // assertEq(_deletedResponse.disputeId, bytes32(0)); // } - -// function test_deleteResponse_afterDeadline(bytes memory _responseData, uint256 _timestamp) public { -// vm.assume(_timestamp > _expectedDeadline); - -// _forBondDepositERC20(_accountingExtension, proposer, usdc, _expectedBondSize, _expectedBondSize); - -// vm.startPrank(proposer); -// _accountingExtension.approveModule(address(_responseModule)); -// bytes32 _responseId = oracle.proposeResponse(_requestId, _responseData); -// vm.stopPrank(); - -// vm.warp(_timestamp); - -// vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooLateToDelete.selector); - -// vm.prank(proposer); -// oracle.deleteResponse(_responseId); -// } - -// function test_proposeResponse_finalizedRequest(bytes memory _responseData, uint256 _timestamp) public { -// vm.assume(_timestamp > _expectedDeadline + _baseDisputeWindow); - -// _forBondDepositERC20(_accountingExtension, proposer, usdc, _expectedBondSize, _expectedBondSize); - -// vm.startPrank(proposer); -// _accountingExtension.approveModule(address(_responseModule)); -// bytes32 _responseId = oracle.proposeResponse(_requestId, _responseData); -// vm.stopPrank(); - -// vm.warp(_timestamp); -// oracle.finalize(_requestId, _responseId); - -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); -// vm.prank(proposer); -// oracle.proposeResponse(_requestId, _responseData); -// } // } diff --git a/solidity/test/mocks/MockVerifier.sol b/solidity/test/mocks/MockVerifier.sol new file mode 100644 index 00000000..829887c1 --- /dev/null +++ b/solidity/test/mocks/MockVerifier.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ITreeVerifier} from '../../interfaces/ITreeVerifier.sol'; + +contract MockVerifier is ITreeVerifier { + constructor() {} + + function calculateRoot( + bytes memory, /* _treeData */ + bytes32[] memory /* _leavesToInsert */ + ) external view returns (bytes32 _calculatedRoot) { + _calculatedRoot = keccak256(abi.encode(block.timestamp)); + } +} diff --git a/solidity/test/unit/modules/dispute/CircuitResolverModule.t.sol b/solidity/test/unit/modules/dispute/CircuitResolverModule.t.sol index 36e325fa..5cc4878a 100644 --- a/solidity/test/unit/modules/dispute/CircuitResolverModule.t.sol +++ b/solidity/test/unit/modules/dispute/CircuitResolverModule.t.sol @@ -1,477 +1,298 @@ -// // 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'; +import {MockVerifier} from '../../../mocks/MockVerifier.sol'; + +/** + * @dev Harness to set an entry in the correctResponses mapping + */ +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 circuit verifier address + MockVerifier public mockVerifier; + + // Events + event DisputeStatusChanged(bytes32 _disputeId, IOracle.Dispute _dispute, IOracle.DisputeStatus _status); + 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'); + + mockVerifier = new MockVerifier(); + + circuitResolverModule = new ForTest_CircuitResolverModule(oracle); + } +} + +contract CircuitResolverModule_Unit_ModuleData is BaseTest { + /** + * @notice Test that the decodeRequestData function returns the correct values + */ + function test_decodeRequestData( + IAccountingExtension _accountingExtension, + IERC20 _randomToken, + uint256 _bondSize, + bytes memory _callData + ) public { + // Mock data + bytes memory _requestData = abi.encode( + ICircuitResolverModule.RequestParameters({ + callData: _callData, + verifier: address(mockVerifier), + 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( + address(_params.accountingExtension), address(_accountingExtension), 'Mismatch: decoded accounting extension' + ); + assertEq(address(_params.bondToken), address(_randomToken), 'Mismatch: decoded token'); + assertEq(_params.verifier, address(mockVerifier), 'Mismatch: decoded circuit verifier'); + assertEq(_params.bondSize, _bondSize, 'Mismatch: decoded bond size'); + assertEq(_params.callData, _callData, 'Mismatch: decoded calldata'); + } + + /** + * @notice Test that the moduleName function returns the correct name + */ + function test_moduleName() public { + assertEq(circuitResolverModule.moduleName(), 'CircuitResolverModule'); + } +} + +contract CircuitResolverModule_Unit_DisputeResponse is BaseTest { + /** + * @notice Test if dispute incorrect response returns the correct status + */ + function test_disputeIncorrectResponse( + IAccountingExtension _accountingExtension, + IERC20 _randomToken, + uint256 _bondSize, + bytes memory _callData + ) public { + _callData = abi.encodeWithSelector(mockVerifier.calculateRoot.selector, _callData); + + mockRequest.disputeModuleData = abi.encode( + ICircuitResolverModule.RequestParameters({ + callData: _callData, + verifier: address(mockVerifier), + accountingExtension: _accountingExtension, + bondToken: _randomToken, + bondSize: _bondSize + }) + ); + + bool _correctResponse = false; + + mockResponse.requestId = _getId(mockRequest); + mockDispute.requestId = mockResponse.requestId; + mockDispute.responseId = _getId(mockResponse); + + // Mock and expect the call to the verifier + _mockAndExpect(address(mockVerifier), _callData, abi.encode(_correctResponse)); + + // 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) + ); + + // Test: call disputeResponse + vm.prank(address(oracle)); + circuitResolverModule.disputeResponse(mockRequest, mockResponse, mockDispute); + } + + function test_emitsEvent( + IAccountingExtension _accountingExtension, + IERC20 _randomToken, + uint256 _bondSize, + bytes memory _callData + ) public { + mockRequest.disputeModuleData = abi.encode( + ICircuitResolverModule.RequestParameters({ + callData: _callData, + verifier: address(mockVerifier), + accountingExtension: _accountingExtension, + bondToken: _randomToken, + bondSize: _bondSize + }) + ); + + bytes32 _requestId = _getId(mockRequest); + bool _correctResponse = false; + + mockResponse.requestId = _requestId; + mockResponse.response = abi.encode(true); + + // Mock and expect the call to the verifier + _mockAndExpect(address(mockVerifier), _callData, abi.encode(_correctResponse)); + + // 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(circuitResolverModule)); + emit ResponseDisputed({ + _requestId: mockResponse.requestId, + _responseId: mockDispute.responseId, + _disputeId: _getId(mockDispute), + _dispute: mockDispute, + _blockNumber: block.number + }); + + vm.prank(address(oracle)); + circuitResolverModule.disputeResponse(mockRequest, mockResponse, mockDispute); + } + + /** + * @notice Test if dispute correct response returns the correct status + */ + function test_disputeCorrectResponse( + IAccountingExtension _accountingExtension, + IERC20 _randomToken, + uint256 _bondSize, + bytes memory _callData + ) public { + _callData = abi.encodeWithSelector(mockVerifier.calculateRoot.selector, _callData); + + mockRequest.disputeModuleData = abi.encode( + ICircuitResolverModule.RequestParameters({ + callData: _callData, + verifier: address(mockVerifier), + accountingExtension: _accountingExtension, + bondToken: _randomToken, + bondSize: _bondSize + }) + ); + + bytes memory _encodedCorrectResponse = abi.encode(true); + + mockResponse.requestId = _getId(mockRequest); + mockResponse.response = _encodedCorrectResponse; + + // Mock and expect the call to the verifier + _mockAndExpect(address(mockVerifier), _callData, _encodedCorrectResponse); + + // 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)); + circuitResolverModule.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: does it revert if not called by the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(_randomCaller); + circuitResolverModule.disputeResponse(mockRequest, mockResponse, mockDispute); + } +} + +contract CircuitResolverModule_Unit_OnDisputeStatusChange is BaseTest { + function test_emitsEvent( + IAccountingExtension _accountingExtension, + IERC20 _randomToken, + uint256 _bondSize, + bytes memory _callData + ) public { + mockRequest.disputeModuleData = abi.encode( + ICircuitResolverModule.RequestParameters({ + callData: _callData, + verifier: address(mockVerifier), + accountingExtension: _accountingExtension, + bondToken: _randomToken, + bondSize: _bondSize + }) + ); + + bytes32 _requestId = _getId(mockRequest); + 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, (mockRequest, mockResponse)), abi.encode(true)); + + // Populate the mock dispute with the correct values + mockDispute.responseId = _getId(mockResponse); + 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, mockRequest, mockResponse, mockDispute); + } +} diff --git a/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol b/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol index 21ec848d..7959bd10 100644 --- a/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol +++ b/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol @@ -1,356 +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({ -// createdAt: block.timestamp, -// disputer: dude, -// responseId: mockId, -// proposer: dude, -// requestId: mockId, -// status: IOracle.DisputeStatus.Active -// }); -// } -// } - -// 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, _responseId, _disputer, _proposer); - -// 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); -// } -// } - -// contract RootVerificationModule_Unit_DisputeEscalated is BaseTest { -// /** -// * @notice Test if dispute escalated do nothing -// */ -// function test_returnCorrectStatus() public { -// // Record sstore and sload -// vm.prank(address(oracle)); -// vm.record(); -// rootVerificationModule.disputeEscalated(mockId); -// (bytes32[] memory _reads, bytes32[] memory _writes) = vm.accesses(address(rootVerificationModule)); - -// // Check: no storage access? -// assertEq(_reads.length, 0); -// assertEq(_writes.length, 0); -// } -// } +// 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); + } +} diff --git a/solidity/test/unit/modules/finality/CallbackModule.t.sol b/solidity/test/unit/modules/finality/CallbackModule.t.sol index 77bf4c8a..6cbf2076 100644 --- a/solidity/test/unit/modules/finality/CallbackModule.t.sol +++ b/solidity/test/unit/modules/finality/CallbackModule.t.sol @@ -1,147 +1,113 @@ -// // SPDX-License-Identifier: AGPL-3.0-only -// pragma solidity ^0.8.19; - -// import 'forge-std/Test.sol'; - -// import {Helpers} from '../../../utils/Helpers.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 {CallbackModule, ICallbackModule} from '../../../../contracts/modules/finality/CallbackModule.sol'; - -// /** -// * @dev Harness to set an entry in the requestData mapping, without triggering setup request hooks -// */ -// contract ForTest_CallbackModule is CallbackModule { -// constructor(IOracle _oracle) CallbackModule(_oracle) {} - -// function forTest_setRequestData(bytes32 _requestId, bytes memory _data) public { -// requestData[_requestId] = _data; -// } -// } - -// /** -// * @title Callback Module Unit tests -// */ -// contract BaseTest is Test, Helpers { -// // The target contract -// ForTest_CallbackModule public callbackModule; -// // A mock oracle -// IOracle public oracle; - -// event Callback(bytes32 indexed _request, address indexed _target, bytes _data); -// 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'); - -// callbackModule = new ForTest_CallbackModule(oracle); -// } -// } - -// contract CallbackModule_Unit_ModuleData is BaseTest { -// /** -// * @notice Test that the moduleName function returns the correct name -// */ -// function test_moduleNameReturnsName() public { -// assertEq(callbackModule.moduleName(), 'CallbackModule'); -// } - -// /** -// * @notice Test that the decodeRequestData function returns the correct values -// */ -// function test_decodeRequestData(bytes32 _requestId, address _target, bytes memory _data) public { -// // Create and set some mock request data -// bytes memory _requestData = abi.encode(ICallbackModule.RequestParameters({target: _target, data: _data})); -// callbackModule.forTest_setRequestData(_requestId, _requestData); - -// // Decode the given request data -// ICallbackModule.RequestParameters memory _params = callbackModule.decodeRequestData(_requestId); - -// // Check: decoded values match original values? -// assertEq(_params.target, _target); -// assertEq(_params.data, _data); -// } -// } - -// contract CallbackModule_Unit_FinalizeRequest is BaseTest { -// /** -// * @notice Test that finalizeRequest calls the _target.callback with the correct data -// */ -// function test_triggersCallback( -// bytes32 _requestId, -// address _target, -// bytes calldata _data -// ) public assumeFuzzable(_target) { -// vm.assume(_target != address(vm)); - -// // Create and set some mock request data -// bytes memory _requestData = abi.encode(ICallbackModule.RequestParameters({target: _target, data: _data})); -// callbackModule.forTest_setRequestData(_requestId, _requestData); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(callbackModule)); -// emit Callback(_requestId, _target, _data); - -// vm.prank(address(oracle)); -// callbackModule.finalizeRequest(_requestId, address(oracle)); -// } - -// function test_emitsEvent(bytes32 _requestId, address _target, bytes calldata _data) public assumeFuzzable(_target) { -// vm.assume(_target != address(vm)); - -// // Create and set some mock request data -// bytes memory _requestData = abi.encode(ICallbackModule.RequestParameters({target: _target, data: _data})); -// callbackModule.forTest_setRequestData(_requestId, _requestData); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(callbackModule)); -// emit RequestFinalized(_requestId, address(oracle)); - -// vm.prank(address(oracle)); -// callbackModule.finalizeRequest(_requestId, address(oracle)); -// } - -// /** -// * @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); -// callbackModule.finalizeRequest(_requestId, address(_caller)); -// } -// } - -// contract CallbackModule_Unit_Setup is BaseTest { -// /** -// * @notice Test that _afterSetupRequest checks if the target address is a contract. -// */ -// function test_revertIfTargetNotContract( -// bytes32 _requestId, -// address _target, -// bool _hasCode, -// bytes calldata _data -// ) public assumeFuzzable(_target) { -// vm.assume(_target.code.length == 0); -// bytes memory _requestData = abi.encode(ICallbackModule.RequestParameters({target: _target, data: _data})); - -// if (_hasCode) { -// vm.etch(_target, hex'069420'); -// } else { -// // Check: does it revert if the target has no code? -// vm.expectRevert(ICallbackModule.CallbackModule_TargetHasNoCode.selector); -// } - -// vm.prank(address(oracle)); -// callbackModule.setupRequest(_requestId, _requestData); -// } -// } +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; + +import 'forge-std/Test.sol'; + +import {Helpers} from '../../../utils/Helpers.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 {CallbackModule, ICallbackModule} from '../../../../contracts/modules/finality/CallbackModule.sol'; + +/** + * @title Callback Module Unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + CallbackModule public callbackModule; + // A mock oracle + IOracle public oracle; + + // Events + event Callback(bytes32 indexed _request, address indexed _target, bytes _data); + + /** + * @notice Deploy the target and mock oracle+accounting extension + */ + function setUp() public { + oracle = IOracle(makeAddr('Oracle')); + vm.etch(address(oracle), hex'069420'); + + callbackModule = new CallbackModule(oracle); + } +} + +contract CallbackModule_Unit_ModuleData is BaseTest { + /** + * @notice Test that the moduleName function returns the correct name + */ + function test_moduleNameReturnsName() public { + assertEq(callbackModule.moduleName(), 'CallbackModule'); + } + + /** + * @notice Test that the decodeRequestData function returns the correct values + */ + function test_decodeRequestData(address _target, bytes memory _data) public { + // Create and set some mock request data + bytes memory _requestData = abi.encode(ICallbackModule.RequestParameters({target: _target, data: _data})); + + // Decode the given request data + ICallbackModule.RequestParameters memory _params = callbackModule.decodeRequestData(_requestData); + + // Check: decoded values match original values? + assertEq(_params.target, _target); + assertEq(_params.data, _data); + } +} + +contract CallbackModule_Unit_FinalizeRequest is BaseTest { + /** + * @notice Test that finalizeRequest emits events + */ + function test_emitsEvents(address _proposer, address _target, bytes calldata _data) public assumeFuzzable(_target) { + mockRequest.finalityModuleData = abi.encode(ICallbackModule.RequestParameters({target: _target, data: _data})); + mockResponse.requestId = _getId(mockRequest); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(callbackModule)); + emit Callback(mockResponse.requestId, _target, _data); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(callbackModule)); + emit RequestFinalized(mockResponse.requestId, mockResponse, _proposer); + + vm.prank(address(oracle)); + callbackModule.finalizeRequest(mockRequest, mockResponse, _proposer); + } + + /** + * @notice Test that finalizeRequest triggers the callback + */ + function test_triggersCallback( + address _proposer, + address _target, + bytes calldata _data + ) public assumeFuzzable(_target) { + mockRequest.finalityModuleData = abi.encode(ICallbackModule.RequestParameters({target: _target, data: _data})); + mockResponse.requestId = _getId(mockRequest); + + // Mock and expect the callback + _mockAndExpect(_target, _data, abi.encode('')); + + vm.prank(address(oracle)); + callbackModule.finalizeRequest(mockRequest, mockResponse, _proposer); + } + + /** + * @notice Test that the finalizeRequest reverts if caller is not the oracle + */ + function test_revertsIfWrongCaller(ICallbackModule.RequestParameters calldata _data, address _caller) public { + vm.assume(_caller != address(oracle)); + + mockRequest.finalityModuleData = abi.encode(_data); + mockResponse.requestId = _getId(mockRequest); + + // Check: does it revert if not called by the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(_caller); + callbackModule.finalizeRequest(mockRequest, mockResponse, _caller); + } +} diff --git a/solidity/test/unit/modules/response/BondedResponseModule.t.sol b/solidity/test/unit/modules/response/BondedResponseModule.t.sol index 9d16f581..30c2133c 100644 --- a/solidity/test/unit/modules/response/BondedResponseModule.t.sol +++ b/solidity/test/unit/modules/response/BondedResponseModule.t.sol @@ -1,461 +1,345 @@ -// // 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 { -// BondedResponseModule, -// IBondedResponseModule, -// IModule, -// IOracle -// } from '../../../../contracts/modules/response/BondedResponseModule.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_BondedResponseModule is BondedResponseModule { -// constructor(IOracle _oracle) BondedResponseModule(_oracle) {} - -// function forTest_setRequestData(bytes32 _requestId, bytes memory _data) public { -// requestData[_requestId] = _data; -// } -// } - -// /** -// * @title Bonded Response Module Unit tests -// */ -// contract BaseTest is Test, Helpers { -// // The target contract -// ForTest_BondedResponseModule public bondedResponseModule; -// // A mock oracle -// IOracle public oracle; -// // A mock accounting extension -// IAccountingExtension public accounting = IAccountingExtension(makeAddr('accounting')); -// // Base dispute window -// uint256 internal _baseDisputeWindow = 12 hours; - -// event ProposeResponse(bytes32 indexed _requestId, address _proposer, bytes _responseData); -// 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'); - -// // vm.etch(address(token), hex'069420'); - -// // Avoid starting at 0 for time sensitive tests -// vm.warp(123_456); - -// bondedResponseModule = new ForTest_BondedResponseModule(oracle); -// } -// } - -// contract BondedResponseModule_Unit_ModuleData is BaseTest { -// /** -// * @notice Test that the moduleName function returns the correct name -// */ -// function test_moduleNameReturnsName() public { -// assertEq(bondedResponseModule.moduleName(), 'BondedResponseModule'); -// } - -// /** -// * @notice Test that the decodeRequestData function returns the correct values -// */ -// function test_decodeRequestData( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _disputeWindow, -// IERC20 _token -// ) public { -// // Create and set some mock request data -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Get the returned values -// IBondedResponseModule.RequestParameters memory _params = bondedResponseModule.decodeRequestData(_requestId); - -// // Check: correct values returned? -// assertEq(address(_params.accountingExtension), address(accounting), 'Mismatch: accounting extension address'); -// assertEq(address(_params.bondToken), address(_token), 'Mismatch: token address'); -// assertEq(_params.bondSize, _bondSize, 'Mismatch: bond size'); -// assertEq(_params.deadline, _deadline, 'Mismatch: deadline'); -// assertEq(_params.disputeWindow, _disputeWindow, 'Mismatch: dispute window'); -// } -// } - -// contract BondedResponseModule_Unit_Setup is BaseTest { -// function test_setupRequestRevertsIfInvalidRequest( -// IAccountingExtension _accounting, -// IERC20 _token, -// uint256 _deadline, -// uint256 _bondSize -// ) public { -// _deadline = bound(_deadline, 0, block.timestamp); - -// IBondedResponseModule.RequestParameters memory _requestParams = IBondedResponseModule.RequestParameters({ -// accountingExtension: _accounting, -// bondToken: _token, -// bondSize: _bondSize, -// deadline: _deadline, -// disputeWindow: _baseDisputeWindow -// }); - -// // Check: does it revert if the provided deadline is invalid? -// vm.expectRevert(IBondedResponseModule.BondedResponseModule_InvalidRequest.selector); -// vm.prank(address(oracle)); - -// bondedResponseModule.setupRequest(bytes32(0), abi.encode(_requestParams)); -// } -// } - -// contract BondedResponseModule_Unit_Propose is BaseTest { -// /** -// * @notice Test that the propose function is only callable by the oracle -// */ -// function test_revertIfNotOracle( -// bytes32 _requestId, -// address _sender, -// address _proposer, -// bytes calldata _responseData -// ) public { -// vm.assume(_sender != address(oracle)); - -// // Check: does it revert if not called by the Oracle? -// vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); - -// vm.prank(address(_sender)); -// bondedResponseModule.propose(_requestId, _proposer, _responseData, _sender); -// } - -// /** -// * @notice Test that the propose function works correctly and triggers _afterPropose (which bonds) -// */ -// function test_propose( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _disputeWindow, -// bytes calldata _responseData, -// address _sender, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); -// _disputeWindow = bound(_disputeWindow, 61, 365 days); -// _bondSize = bound(_bondSize, 0, type(uint248).max); - -// // Create and set some mock request data -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.getResponseIds to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponseIds, _requestId), abi.encode(new bytes32[](0))); - -// // Mock and expect IAccountingExtension.bond to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeWithSignature( -// 'bond(address,bytes32,address,uint256,address)', _proposer, _requestId, _token, _bondSize, _sender -// ), -// abi.encode() -// ); - -// vm.prank(address(oracle)); -// IOracle.Response memory _responseReturned = -// bondedResponseModule.propose(_requestId, _proposer, _responseData, _sender); - -// IOracle.Response memory _responseExpected = IOracle.Response({ -// createdAt: block.timestamp, -// requestId: _requestId, -// disputeId: bytes32(''), -// proposer: _proposer, -// response: _responseData -// }); - -// // Check: correct response struct returned? -// assertEq(_responseReturned.requestId, _responseExpected.requestId, 'Mismatch: request ID'); -// assertEq(_responseReturned.disputeId, _responseExpected.disputeId, 'Mismatch: dispute ID'); -// assertEq(_responseReturned.proposer, _responseExpected.proposer, 'Mismatch: proposer address'); -// assertEq(_responseReturned.response, _responseExpected.response, 'Mismatch: response object'); -// } - -// function test_emitsEvent( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _disputeWindow, -// bytes calldata _responseData, -// address _sender, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); -// _disputeWindow = bound(_disputeWindow, 61, 365 days); - -// // Create and set some mock request data -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.getResponseIds to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponseIds, _requestId), abi.encode(new bytes32[](0))); - -// // Mock and expect IOracle.getResponseIds to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeWithSignature( -// 'bond(address,bytes32,address,uint256,address)', _proposer, _requestId, _token, _bondSize, _sender -// ), -// abi.encode() -// ); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(bondedResponseModule)); -// emit ProposeResponse(_requestId, _proposer, _responseData); - -// vm.prank(address(oracle)); -// bondedResponseModule.propose(_requestId, _proposer, _responseData, _sender); -// } -// } - -// contract BondedResponseModule_Unit_FinalizeRequest is BaseTest { -// /** -// * @notice Test that the propose function is only callable by the oracle -// */ -// function test_calls( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _disputeWindow, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); -// _disputeWindow = bound(_disputeWindow, 61, 365 days); - -// // Check revert if deadline has not passed -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.allowedModule to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(false) -// ); - -// // Check: does it revert if it's too early to finalize? -// vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooEarlyToFinalize.selector); - -// vm.prank(address(oracle)); -// bondedResponseModule.finalizeRequest(_requestId, address(this)); - -// // Check correct calls are made if deadline has passed -// _deadline = block.timestamp; - -// _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// requestId: _requestId, -// disputeId: bytes32(''), -// proposer: _proposer, -// response: bytes('bleh') -// }); - -// // Mock and expect IOracle.getFinalizedResponse to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, _requestId), abi.encode(_mockResponse)); - -// // Mock and expect IAccountingExtension.release to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.release, (_proposer, _requestId, _token, _bondSize)), -// abi.encode(true) -// ); - -// vm.warp(block.timestamp + _disputeWindow); - -// vm.prank(address(oracle)); -// bondedResponseModule.finalizeRequest(_requestId, address(this)); -// } - -// function test_emitsEvent( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _disputeWindow, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); -// _disputeWindow = bound(_disputeWindow, 61, 365 days); - -// // Check revert if deadline has not passed -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.allowedModule to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(false) -// ); - -// // Check correct calls are made if deadline has passed -// _deadline = block.timestamp; - -// _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// requestId: _requestId, -// disputeId: bytes32(''), -// proposer: _proposer, -// response: bytes('bleh') -// }); - -// // Mock and expect IOracle.getFinalizedResponse to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, _requestId), abi.encode(_mockResponse)); - -// // Mock and expect IAccountingExtension.release to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.release, (_proposer, _requestId, _token, _bondSize)), -// abi.encode(true) -// ); - -// // Check: is event emitted? -// vm.expectEmit(true, true, true, true, address(bondedResponseModule)); -// emit RequestFinalized(_requestId, address(this)); - -// vm.warp(block.timestamp + _disputeWindow); - -// vm.prank(address(oracle)); -// bondedResponseModule.finalizeRequest(_requestId, address(this)); -// } - -// /** -// * @notice Test that the finalize function can be called by an allowed module before the time window. -// */ -// function test_earlyByModule( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); - -// address _allowedModule = makeAddr('allowed module'); -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _baseDisputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.allowedModule to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, _allowedModule)), abi.encode(true) -// ); - -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// requestId: _requestId, -// disputeId: bytes32(''), -// proposer: _proposer, -// response: bytes('bleh') -// }); - -// // Mock and expect IOracle.getFinalizedResponse to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, _requestId), abi.encode(_mockResponse)); - -// // Mock and expect IAccountingExtension.release to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.release, (_proposer, _requestId, _token, _bondSize)), -// abi.encode(true) -// ); - -// vm.prank(address(oracle)); -// bondedResponseModule.finalizeRequest(_requestId, _allowedModule); -// } - -// /** -// * @notice Test that the finalizing a request during a response dispute window will revert. -// */ -// function test_revertDuringDisputeWindow( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); - -// address _finalizer = makeAddr('finalizer'); -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _baseDisputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.allowedModule to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, _finalizer)), abi.encode(false)); - -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: _deadline - 1, -// requestId: _requestId, -// disputeId: bytes32(''), -// proposer: _proposer, -// response: bytes('bleh') -// }); - -// // Mock and expect IOracle.getFinalizedResponse to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, _requestId), abi.encode(_mockResponse)); - -// vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooEarlyToFinalize.selector); - -// vm.warp(_deadline + 1); -// vm.prank(address(oracle)); -// bondedResponseModule.finalizeRequest(_requestId, _finalizer); -// } -// } - -// contract BondedResponseModule_Unit_DeleteResponse is BaseTest { -// /** -// * @notice Test that the delete response function triggers bond release. -// */ -// function test_deleteResponse( -// bytes32 _requestId, -// bytes32 _responseId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _timestamp, -// IERC20 _token, -// address _proposer -// ) public { -// _timestamp = bound(_timestamp, 1, type(uint248).max); - -// // Create and set some mock request data -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _baseDisputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// vm.warp(_timestamp); - -// if (_deadline >= _timestamp) { -// // Mock and expect IAccountingExtension.release to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.release, (_proposer, _requestId, _token, _bondSize)), -// abi.encode() -// ); -// } else { -// // Check: does it revert if the deadline has passed? -// vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooLateToDelete.selector); -// } - -// vm.prank(address(oracle)); -// bondedResponseModule.deleteResponse(_requestId, _responseId, _proposer); -// } -// } +// 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 { + BondedResponseModule, + IBondedResponseModule, + IModule, + IOracle +} from '../../../../contracts/modules/response/BondedResponseModule.sol'; + +import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountingExtension.sol'; + +/** + * @title Bonded Response Module Unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + BondedResponseModule public bondedResponseModule; + // A mock oracle + IOracle public oracle; + // A mock accounting extension + IAccountingExtension public accounting = IAccountingExtension(makeAddr('Accounting')); + // Base dispute window + uint256 internal _baseDisputeWindow = 12 hours; + + // Events + event ResponseProposed(bytes32 indexed _requestId, IOracle.Response _response, uint256 indexed _blockNumber); + + /** + * @notice Deploy the target and mock oracle+accounting extension + */ + function setUp() public { + oracle = IOracle(makeAddr('Oracle')); + vm.etch(address(oracle), hex'069420'); + + // Avoid starting at 0 for time sensitive tests + vm.warp(123_456); + + bondedResponseModule = new BondedResponseModule(oracle); + } +} + +contract BondedResponseModule_Unit_ModuleData is BaseTest { + /** + * @notice Test that the moduleName function returns the correct name + */ + function test_moduleNameReturnsName() public { + assertEq(bondedResponseModule.moduleName(), 'BondedResponseModule'); + } + + /** + * @notice Test that the decodeRequestData function returns the correct values + */ + function test_decodeRequestData(IERC20 _token, uint256 _bondSize, uint256 _deadline, uint256 _disputeWindow) public { + // Create and set some mock request data + bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + + // Get the returned values + IBondedResponseModule.RequestParameters memory _params = bondedResponseModule.decodeRequestData(_data); + + // Check: correct values returned? + assertEq(address(_params.accountingExtension), address(accounting), 'Mismatch: accounting extension address'); + assertEq(address(_params.bondToken), address(_token), 'Mismatch: token address'); + assertEq(_params.bondSize, _bondSize, 'Mismatch: bond size'); + assertEq(_params.deadline, _deadline, 'Mismatch: deadline'); + assertEq(_params.disputeWindow, _disputeWindow, 'Mismatch: dispute window'); + } +} + +contract BondedResponseModule_Unit_Propose is BaseTest { + /** + * @notice Test that the propose function is only callable by the oracle + */ + function test_revertIfNotOracle(address _sender) public { + vm.assume(_sender != address(oracle)); + + // Check: does it revert if not called by the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(address(_sender)); + bondedResponseModule.propose(mockRequest, mockResponse, _sender); + } + + /** + * @notice Test that the propose function works correctly and bonds the proposer's funds + */ + function test_propose( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + uint256 _disputeWindow, + address _sender, + address _proposer + ) public assumeFuzzable(_sender) assumeFuzzable(_proposer) { + _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); + _disputeWindow = bound(_disputeWindow, 61, 365 days); + _bondSize = bound(_bondSize, 0, type(uint248).max); + + // Set the response module parameters + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.getResponseIds to be called + _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponseIds, _requestId), abi.encode(new bytes32[](0))); + + // Mock and expect IAccountingExtension.bond to be called + _mockAndExpect( + address(accounting), + abi.encodeWithSignature( + 'bond(address,bytes32,address,uint256,address)', _proposer, _requestId, _token, _bondSize, _sender + ), + abi.encode() + ); + + vm.prank(address(oracle)); + bondedResponseModule.propose(mockRequest, mockResponse, _sender); + } + + function test_emitsEvent( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + uint256 _disputeWindow, + address _sender, + address _proposer + ) public { + _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); + _disputeWindow = bound(_disputeWindow, 61, 365 days); + + // Create and set some mock request data + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.getResponseIds to be called + _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponseIds, _requestId), abi.encode(new bytes32[](0))); + + // Mock and expect IOracle.getResponseIds to be called + _mockAndExpect( + address(accounting), + abi.encodeWithSignature( + 'bond(address,bytes32,address,uint256,address)', _proposer, _requestId, _token, _bondSize, _sender + ), + abi.encode() + ); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(bondedResponseModule)); + emit ResponseProposed({_requestId: _requestId, _response: mockResponse, _blockNumber: block.number}); + + vm.prank(address(oracle)); + bondedResponseModule.propose(mockRequest, mockResponse, _sender); + } +} + +contract BondedResponseModule_Unit_FinalizeRequest is BaseTest { + /** + * @notice Test that the propose function is only callable by the oracle + */ + function test_revertIfNotOracle(address _sender) public { + vm.assume(_sender != address(oracle)); + + // Check: does it revert if not called by the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(address(_sender)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, _sender); + } + + function test_revertsBeforeDeadline( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + uint256 _disputeWindow, + address _proposer + ) public { + _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); + _disputeWindow = bound(_disputeWindow, 61, 365 days); + + // Check revert if deadline has not passed + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + mockResponse.requestId = _getId(mockRequest); + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.allowedModule to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.allowedModule, (_getId(mockRequest), address(this))), abi.encode(false) + ); + + // Check: does it revert if it's too early to finalize? + vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooEarlyToFinalize.selector); + + vm.prank(address(oracle)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, address(this)); + } + + function test_releasesBond( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + uint256 _disputeWindow, + address _proposer + ) public { + _disputeWindow = bound(_disputeWindow, 61, 365 days); + + // Check correct calls are made if deadline has passed + _deadline = block.timestamp; + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + mockResponse.requestId = _getId(mockRequest); + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.allowedModule to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.allowedModule, (_getId(mockRequest), address(this))), abi.encode(true) + ); + + // Mock and expect IOracle.createdAt to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.createdAt, (_getId(mockResponse))), abi.encode(block.timestamp) + ); + + // Mock and expect IAccountingExtension.release to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.release, (_proposer, _getId(mockRequest), _token, _bondSize)), + abi.encode(true) + ); + + vm.warp(block.timestamp + _disputeWindow); + + vm.prank(address(oracle)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, address(this)); + } + + function test_emitsEvent(IERC20 _token, uint256 _bondSize, uint256 _disputeWindow, address _proposer) public { + _disputeWindow = bound(_disputeWindow, 61, 365 days); + + // Check correct calls are made if deadline has passed + uint256 _deadline = block.timestamp; + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.allowedModule to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(false) + ); + + // Mock and expect IOracle.createdAt to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.createdAt, (_getId(mockResponse))), abi.encode(block.timestamp) + ); + + // Mock and expect IAccountingExtension.release to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.release, (_proposer, _getId(mockRequest), _token, _bondSize)), + abi.encode(true) + ); + + // Check: is event emitted? + vm.expectEmit(true, true, true, true, address(bondedResponseModule)); + emit RequestFinalized({_requestId: _getId(mockRequest), _response: mockResponse, _finalizer: address(this)}); + + vm.warp(block.timestamp + _disputeWindow); + + vm.prank(address(oracle)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, address(this)); + } + + /** + * @notice Test that the finalize function can be called by an allowed module before the time window. + */ + function test_earlyByModule( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + address _proposer, + address _allowedModule + ) public assumeFuzzable(_allowedModule) { + _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _baseDisputeWindow); + + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.allowedModule to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, _allowedModule)), abi.encode(true) + ); + + // Mock and expect IOracle.createdAt to be called + _mockAndExpect( + address(oracle), + abi.encodeCall(IOracle.createdAt, (_getId(mockResponse))), + abi.encode(block.timestamp - _baseDisputeWindow) + ); + + // Mock and expect IAccountingExtension.release to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.release, (_proposer, _requestId, _token, _bondSize)), + abi.encode(true) + ); + + vm.prank(address(oracle)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, _allowedModule); + } + + /** + * @notice Test that the finalizing a request during a response dispute window will revert. + */ + function test_revertDuringDisputeWindow( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + address _finalizer + ) public { + _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); + + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _baseDisputeWindow); + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + + // Mock and expect IOracle.allowedModule to be called + _mockAndExpect(address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, _finalizer)), abi.encode(false)); + + vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooEarlyToFinalize.selector); + + vm.prank(address(oracle)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, _finalizer); + } +} 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)); } diff --git a/yarn.lock b/yarn.lock index 23515a4a..b61d241d 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-48b0248d": - version "0.0.0-48b0248d" - resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-core-contracts/-/prophet-core-contracts-0.0.0-48b0248d.tgz#16d0473360074f17b66199c8e3660b71a3d72ad4" - integrity sha512-bEufdaPkLcg1VuYpTWRB5Xf4pmpV3wi0487taGI4A+YtwhMsIh9ZCNPdgWssqLvIfMPlB4FdGOM936l1yqAKYQ== +"@defi-wonderland/prophet-core-contracts@0.0.0-1ae08a81": + version "0.0.0-1ae08a81" + resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-core-contracts/-/prophet-core-contracts-0.0.0-1ae08a81.tgz#77d8f91303c8496556c0cedc12eeaa7abaa5f6fb" + integrity sha512-hMOBUsXR39NXfhExV3+xDBaUYv/6EYdyTrS/VMMSh0Gpbeqql4cIlUHnJzhmc6b+sbIgREq1K44v++8Aminrvw== 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" @@ -3261,9 +3261,9 @@ solhint@3.5.1: prettier "^2.8.3" solidity-ast@^0.4.38: - version "0.4.52" - resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.52.tgz#9f1a9abc7e5ba28bbf91146ecd07aec7e70f3c85" - integrity sha512-iOya9BSiB9jhM8Vf40n8lGELGzwrUc57rl5BhfNtJ5cvAaMvRcNlHeAMNvqJJyjoUnczqRbHqdivEqK89du3Cw== + version "0.4.53" + resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.53.tgz#098259472fccd234ff00f050afaf7843a7ccd635" + integrity sha512-/7xYF//mAt4iP9S21fCFSLMouYXzXJqrd84jbI1LHL1rq7XhyFLUXxVcRkl9KqEEQmI+DmDbTeS6W5cEwcJ2wQ== dependencies: array.prototype.findlast "^1.2.2"