From 6e3e78be597ba5c6a11ee9efeecd9e8fb2849c46 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:26:31 +0400 Subject: [PATCH 1/6] feat: uncomment helpers --- .../extensions/IAccountingExtension.sol | 380 +++++++++--------- solidity/test/utils/Helpers.sol | 122 +++--- 2 files changed, 256 insertions(+), 246 deletions(-) diff --git a/solidity/interfaces/extensions/IAccountingExtension.sol b/solidity/interfaces/extensions/IAccountingExtension.sol index 5a42243e..74c9c9cd 100644 --- a/solidity/interfaces/extensions/IAccountingExtension.sol +++ b/solidity/interfaces/extensions/IAccountingExtension.sol @@ -1,190 +1,190 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; - -// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; - -// /* -// * @title AccountingExtension -// * @notice Extension allowing users to deposit and bond funds -// * to be used for payments and disputes. -// */ -// interface IAccountingExtension { -// /*/////////////////////////////////////////////////////////////// -// EVENTS -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice A user deposited tokens into the accounting extension -// * @param _depositor The user who deposited the tokens -// * @param _token The address of the token deposited by the user -// * @param _amount The amount of `_token` deposited -// */ -// event Deposited(address indexed _depositor, IERC20 indexed _token, uint256 _amount); - -// /** -// * @notice A user withdrew tokens from the accounting extension -// * @param _withdrawer The user who withdrew the tokens -// * @param _token The address of the token withdrawn by the user -// * @param _amount The amount of `_token` withdrawn -// */ -// event Withdrew(address indexed _withdrawer, IERC20 indexed _token, uint256 _amount); - -// /** -// * @notice A payment between users has been made -// * @param _beneficiary The user receiving the tokens -// * @param _payer The user who is getting its tokens transferred -// * @param _token The address of the token being transferred -// * @param _amount The amount of `_token` transferred -// */ -// event Paid( -// bytes32 indexed _requestId, address indexed _beneficiary, address indexed _payer, IERC20 _token, uint256 _amount -// ); - -// /** -// * @notice User's funds have been bonded -// * @param _bonder The user who is getting its tokens bonded -// * @param _token The address of the token being bonded -// * @param _amount The amount of `_token` bonded -// */ -// event Bonded(bytes32 indexed _requestId, address indexed _bonder, IERC20 indexed _token, uint256 _amount); - -// /** -// * @notice User's funds have been released -// * @param _beneficiary The user who is getting its tokens released -// * @param _token The address of the token being released -// * @param _amount The amount of `_token` released -// */ -// event Released(bytes32 indexed _requestId, address indexed _beneficiary, IERC20 indexed _token, uint256 _amount); - -// /*/////////////////////////////////////////////////////////////// -// ERRORS -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice Thrown when the account doesn't have enough balance to bond/withdraw -// * or not enough bonded to release/pay -// */ -// error AccountingExtension_InsufficientFunds(); - -// /** -// * @notice Thrown when the module bonding user tokens hasn't been approved by the user. -// */ -// error AccountingExtension_InsufficientAllowance(); - -// /** -// * @notice Thrown when an `onlyAllowedModule` function is called by something -// * else than a module being used in the corresponding request -// */ -// error AccountingExtension_UnauthorizedModule(); - -// /** -// * @notice Thrown when an `onlyParticipant` function is called with an address -// * that is not part of the request. -// */ -// error AccountingExtension_UnauthorizedUser(); - -// /*/////////////////////////////////////////////////////////////// -// VARIABLES -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice Returns the interface for the Oracle contract -// */ -// function ORACLE() external view returns (IOracle _oracle); - -// /** -// * @notice Returns the amount of a token a user has bonded -// * @param _user The address of the user with bonded tokens -// * @param _bondToken The token bonded -// * @param _requestId The id of the request the user bonded for -// * @return _amount The amount of `_bondToken` bonded -// */ -// function bondedAmountOf(address _user, IERC20 _bondToken, bytes32 _requestId) external returns (uint256 _amount); - -// /** -// * @notice Returns the amount of a token a user has deposited -// * @param _user The address of the user with deposited tokens -// * @param _token The token deposited -// * @return _amount The amount of `_token` deposited -// */ -// function balanceOf(address _user, IERC20 _token) external view returns (uint256 _amount); - -// /*/////////////////////////////////////////////////////////////// -// LOGIC -// //////////////////////////////////////////////////////////////*/ - -// /** -// * @notice Transfers tokens from a user and updates his virtual balance -// * @dev The user must have approved the accounting extension to transfer the tokens. -// * @param _token The address of the token being deposited -// * @param _amount The amount of `_token` to deposit -// */ -// function deposit(IERC20 _token, uint256 _amount) external; - -// /** -// * @notice Allows an user to withdraw deposited tokens -// * @param _token The address of the token being withdrawn -// * @param _amount The amount of `_token` to withdraw -// */ -// function withdraw(IERC20 _token, uint256 _amount) external; - -// /** -// * @notice Allows a allowed module to transfer bonded tokens from one user to another -// * @dev Only the virtual balances in the accounting extension are modified. The token contract -// * is not called nor its balances modified. -// * @param _requestId The id of the request handling the user's tokens -// * @param _payer The address of the user paying the tokens -// * @param _receiver The address of the user receiving the tokens -// * @param _token The address of the token being transferred -// * @param _amount The amount of `_token` being transferred -// */ -// function pay(bytes32 _requestId, address _payer, address _receiver, IERC20 _token, uint256 _amount) external; - -// /** -// * @notice Allows a allowed module to bond a user's tokens for a request -// * @param _bonder The address of the user to bond tokens for -// * @param _requestId The id of the request the user is bonding for -// * @param _token The address of the token being bonded -// * @param _amount The amount of `_token` to bond -// */ -// function bond(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount) external; - -// /** -// * @notice Allows a valid module to bond a user's tokens for a request -// * @param _bonder The address of the user to bond tokens for -// * @param _requestId The id of the request the user is bonding for -// * @param _token The address of the token being bonded -// * @param _amount The amount of `_token` to bond -// * @param _sender The address starting the propose call on the Oracle -// */ -// function bond(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount, address _sender) external; - -// /** -// * @notice Allows a valid module to release a user's tokens -// * @param _bonder The address of the user to release tokens for -// * @param _requestId The id of the request where the tokens were bonded -// * @param _token The address of the token being released -// * @param _amount The amount of `_token` to release -// */ -// function release(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount) external; - -// /** -// * @notice Allows a user to approve a module for bonding tokens -// * @param _module The address of the module to be approved -// */ -// function approveModule(address _module) external; - -// /** -// * @notice Allows a user to revoke a module's approval for bonding tokens -// * @param _module The address of the module to be revoked -// */ -// function revokeModule(address _module) external; - -// /** -// * @notice Returns a list of all modules a user has approved -// * @param _user The address of the user -// * @return _approvedModules The array of all modules approved by the user -// */ -// function approvedModules(address _user) external view returns (address[] memory _approvedModules); -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; + +/* + * @title AccountingExtension + * @notice Extension allowing users to deposit and bond funds + * to be used for payments and disputes. + */ +interface IAccountingExtension { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice A user deposited tokens into the accounting extension + * @param _depositor The user who deposited the tokens + * @param _token The address of the token deposited by the user + * @param _amount The amount of `_token` deposited + */ + event Deposited(address indexed _depositor, IERC20 indexed _token, uint256 _amount); + + /** + * @notice A user withdrew tokens from the accounting extension + * @param _withdrawer The user who withdrew the tokens + * @param _token The address of the token withdrawn by the user + * @param _amount The amount of `_token` withdrawn + */ + event Withdrew(address indexed _withdrawer, IERC20 indexed _token, uint256 _amount); + + /** + * @notice A payment between users has been made + * @param _beneficiary The user receiving the tokens + * @param _payer The user who is getting its tokens transferred + * @param _token The address of the token being transferred + * @param _amount The amount of `_token` transferred + */ + event Paid( + bytes32 indexed _requestId, address indexed _beneficiary, address indexed _payer, IERC20 _token, uint256 _amount + ); + + /** + * @notice User's funds have been bonded + * @param _bonder The user who is getting its tokens bonded + * @param _token The address of the token being bonded + * @param _amount The amount of `_token` bonded + */ + event Bonded(bytes32 indexed _requestId, address indexed _bonder, IERC20 indexed _token, uint256 _amount); + + /** + * @notice User's funds have been released + * @param _beneficiary The user who is getting its tokens released + * @param _token The address of the token being released + * @param _amount The amount of `_token` released + */ + event Released(bytes32 indexed _requestId, address indexed _beneficiary, IERC20 indexed _token, uint256 _amount); + + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Thrown when the account doesn't have enough balance to bond/withdraw + * or not enough bonded to release/pay + */ + error AccountingExtension_InsufficientFunds(); + + /** + * @notice Thrown when the module bonding user tokens hasn't been approved by the user. + */ + error AccountingExtension_InsufficientAllowance(); + + /** + * @notice Thrown when an `onlyAllowedModule` function is called by something + * else than a module being used in the corresponding request + */ + error AccountingExtension_UnauthorizedModule(); + + /** + * @notice Thrown when an `onlyParticipant` function is called with an address + * that is not part of the request. + */ + error AccountingExtension_UnauthorizedUser(); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Returns the interface for the Oracle contract + */ + function ORACLE() external view returns (IOracle _oracle); + + /** + * @notice Returns the amount of a token a user has bonded + * @param _user The address of the user with bonded tokens + * @param _bondToken The token bonded + * @param _requestId The id of the request the user bonded for + * @return _amount The amount of `_bondToken` bonded + */ + function bondedAmountOf(address _user, IERC20 _bondToken, bytes32 _requestId) external returns (uint256 _amount); + + /** + * @notice Returns the amount of a token a user has deposited + * @param _user The address of the user with deposited tokens + * @param _token The token deposited + * @return _amount The amount of `_token` deposited + */ + function balanceOf(address _user, IERC20 _token) external view returns (uint256 _amount); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Transfers tokens from a user and updates his virtual balance + * @dev The user must have approved the accounting extension to transfer the tokens. + * @param _token The address of the token being deposited + * @param _amount The amount of `_token` to deposit + */ + function deposit(IERC20 _token, uint256 _amount) external; + + /** + * @notice Allows an user to withdraw deposited tokens + * @param _token The address of the token being withdrawn + * @param _amount The amount of `_token` to withdraw + */ + function withdraw(IERC20 _token, uint256 _amount) external; + + /** + * @notice Allows a allowed module to transfer bonded tokens from one user to another + * @dev Only the virtual balances in the accounting extension are modified. The token contract + * is not called nor its balances modified. + * @param _requestId The id of the request handling the user's tokens + * @param _payer The address of the user paying the tokens + * @param _receiver The address of the user receiving the tokens + * @param _token The address of the token being transferred + * @param _amount The amount of `_token` being transferred + */ + function pay(bytes32 _requestId, address _payer, address _receiver, IERC20 _token, uint256 _amount) external; + + /** + * @notice Allows a allowed module to bond a user's tokens for a request + * @param _bonder The address of the user to bond tokens for + * @param _requestId The id of the request the user is bonding for + * @param _token The address of the token being bonded + * @param _amount The amount of `_token` to bond + */ + function bond(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount) external; + + /** + * @notice Allows a valid module to bond a user's tokens for a request + * @param _bonder The address of the user to bond tokens for + * @param _requestId The id of the request the user is bonding for + * @param _token The address of the token being bonded + * @param _amount The amount of `_token` to bond + * @param _sender The address starting the propose call on the Oracle + */ + function bond(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount, address _sender) external; + + /** + * @notice Allows a valid module to release a user's tokens + * @param _bonder The address of the user to release tokens for + * @param _requestId The id of the request where the tokens were bonded + * @param _token The address of the token being released + * @param _amount The amount of `_token` to release + */ + function release(address _bonder, bytes32 _requestId, IERC20 _token, uint256 _amount) external; + + /** + * @notice Allows a user to approve a module for bonding tokens + * @param _module The address of the module to be approved + */ + function approveModule(address _module) external; + + /** + * @notice Allows a user to revoke a module's approval for bonding tokens + * @param _module The address of the module to be revoked + */ + function revokeModule(address _module) external; + + /** + * @notice Returns a list of all modules a user has approved + * @param _user The address of the user + * @return _approvedModules The array of all modules approved by the user + */ + function approvedModules(address _user) external view returns (address[] memory _approvedModules); +} diff --git a/solidity/test/utils/Helpers.sol b/solidity/test/utils/Helpers.sol index 46874489..af32e439 100644 --- a/solidity/test/utils/Helpers.sol +++ b/solidity/test/utils/Helpers.sol @@ -1,56 +1,66 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; - -// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -// import {DSTestPlus} from '@defi-wonderland/solidity-utils/solidity/test/DSTestPlus.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; - -// import {IAccountingExtension} from '../../interfaces/extensions/IAccountingExtension.sol'; - -// contract Helpers is DSTestPlus { -// modifier assumeFuzzable(address _address) { -// _assumeFuzzable(_address); -// _; -// } - -// function _assumeFuzzable(address _address) internal pure { -// assumeNotForgeAddress(_address); -// assumeNotZeroAddress(_address); -// assumeNotPrecompile(_address); -// } - -// function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { -// vm.mockCall(_receiver, _calldata, _returned); -// vm.expectCall(_receiver, _calldata); -// } - -// function _getMockDispute( -// bytes32 _requestId, -// address _disputer, -// address _proposer -// ) internal view returns (IOracle.Dispute memory _dispute) { -// _dispute = IOracle.Dispute({ -// disputer: _disputer, -// responseId: bytes32('response'), -// proposer: _proposer, -// requestId: _requestId, -// status: IOracle.DisputeStatus.None, -// createdAt: block.timestamp -// }); -// } - -// function _forBondDepositERC20( -// IAccountingExtension _accountingExtension, -// address _depositor, -// IERC20 _token, -// uint256 _depositAmount, -// uint256 _balanceIncrease -// ) internal { -// vm.assume(_balanceIncrease >= _depositAmount); -// deal(address(_token), _depositor, _balanceIncrease); -// vm.startPrank(_depositor); -// _token.approve(address(_accountingExtension), _depositAmount); -// _accountingExtension.deposit(_token, _depositAmount); -// vm.stopPrank(); -// } -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {DSTestPlus} from '@defi-wonderland/solidity-utils/solidity/test/DSTestPlus.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; + +import {IAccountingExtension} from '../../interfaces/extensions/IAccountingExtension.sol'; + +contract Helpers is DSTestPlus { + modifier assumeFuzzable(address _address) { + _assumeFuzzable(_address); + _; + } + + function _assumeFuzzable(address _address) internal pure { + assumeNotForgeAddress(_address); + assumeNotZeroAddress(_address); + assumeNotPrecompile(_address); + } + + function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { + vm.mockCall(_receiver, _calldata, _returned); + vm.expectCall(_receiver, _calldata); + } + + function _getMockDispute( + bytes32 _requestId, + address _disputer, + address _proposer + ) internal view returns (IOracle.Dispute memory _dispute) { + _dispute = IOracle.Dispute({ + disputer: _disputer, + responseId: bytes32('response'), + proposer: _proposer, + requestId: _requestId + }); + } + + function _forBondDepositERC20( + IAccountingExtension _accountingExtension, + address _depositor, + IERC20 _token, + uint256 _depositAmount, + uint256 _balanceIncrease + ) internal { + vm.assume(_balanceIncrease >= _depositAmount); + deal(address(_token), _depositor, _balanceIncrease); + vm.startPrank(_depositor); + _token.approve(address(_accountingExtension), _depositAmount); + _accountingExtension.deposit(_token, _depositAmount); + vm.stopPrank(); + } + + function _getId(IOracle.Response memory _response) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_response)); + } + + function _getId(IOracle.Request memory _request) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_request)); + } + + function _getId(IOracle.Dispute memory _dispute) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_dispute)); + } +} From ddf6f72f8c3a34459e73dbb784f7bf3822ecc4ec Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:08:34 +0400 Subject: [PATCH 2/6] feat: update `prophet-core` package --- package.json | 2 +- yarn.lock | 72 +++++++++++++++++++++++++--------------------------- 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index b7700bc1..e85440a1 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "package.json": "sort-package-json" }, "dependencies": { - "@defi-wonderland/prophet-core-contracts": "0.0.0-d05a00d0", + "@defi-wonderland/prophet-core-contracts": "0.0.0-48b0248d", "@defi-wonderland/solidity-utils": "0.0.0-3e9c8e8b", "@openzeppelin/contracts": "^4.9.3", "ds-test": "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0", diff --git a/yarn.lock b/yarn.lock index c61b7709..a6dd1042 100644 --- a/yarn.lock +++ b/yarn.lock @@ -192,10 +192,10 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@defi-wonderland/prophet-core-contracts@0.0.0-d05a00d0": - version "0.0.0-d05a00d0" - resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-core-contracts/-/prophet-core-contracts-0.0.0-d05a00d0.tgz#1357d917fe46a5a12faa67f557e990255dda14fd" - integrity sha512-F/y0r/qDLFACzsN7Y2VRAPIS9Yhx2btU/m7cQT7T84TbIxAmBGVw6/7nb+HeIbXh+QDO90RP6vHAdQOow/q1Xw== +"@defi-wonderland/prophet-core-contracts@0.0.0-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== dependencies: "@defi-wonderland/solidity-utils" "0.0.0-3e9c8e8b" "@openzeppelin/contracts" "^4.9.3" @@ -324,9 +324,9 @@ ts-essentials "^7.0.1" "@types/minimist@^1.2.0": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.4.tgz#81f886786411c45bba3f33e781ab48bd56bfca2e" - integrity sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ== + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" + integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== "@types/node@20.5.1": version "20.5.1" @@ -334,9 +334,9 @@ integrity sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg== "@types/normalize-package-data@^2.4.0": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz#291c243e4b94dbfbc0c0ee26b7666f1d5c030e2c" - integrity sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg== + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== "@types/prettier@^2.1.1": version "2.7.3" @@ -357,9 +357,9 @@ acorn-jsx@^5.0.0: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + version "8.3.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f" + integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== acorn@^6.0.7: version "6.4.2" @@ -367,9 +367,9 @@ acorn@^6.0.7: integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== acorn@^8.4.1: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== add-stream@^1.0.0: version "1.0.0" @@ -1181,14 +1181,10 @@ dotgitignore@^2.1.0: find-up "^3.0.0" minimatch "^3.0.4" -"ds-test@git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0": - version "1.0.0" - resolved "git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0" - -"ds-test@https://github.com/dapphub/ds-test": +"ds-test@git+https://github.com/dapphub/ds-test.git", "ds-test@git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0": version "1.0.0" uid e282159d5170298eb2455a6c05280ab5a73a4ef0 - resolved "https://github.com/dapphub/ds-test#e282159d5170298eb2455a6c05280ab5a73a4ef0" + resolved "git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0" "ds-test@https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0": version "1.0.0" @@ -1457,9 +1453,9 @@ fast-diff@^1.1.2, fast-diff@^1.2.0: integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== fast-glob@^3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -1570,14 +1566,14 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +"forge-std@git+https://github.com/foundry-rs/forge-std.git": + version "1.7.1" + resolved "git+https://github.com/foundry-rs/forge-std.git#267acd30a625086b3f16e1a28cfe0c5097fa46b8" + "forge-std@git+https://github.com/foundry-rs/forge-std.git#e8a047e3f40f13fa37af6fe14e6e06283d9a060e": version "1.5.6" resolved "git+https://github.com/foundry-rs/forge-std.git#e8a047e3f40f13fa37af6fe14e6e06283d9a060e" -"forge-std@https://github.com/foundry-rs/forge-std": - version "1.7.1" - resolved "https://github.com/foundry-rs/forge-std#267acd30a625086b3f16e1a28cfe0c5097fa46b8" - "forge-std@https://github.com/foundry-rs/forge-std.git#f73c73d2018eb6a111f35e4dae7b4f27401e9421": version "1.7.1" resolved "https://github.com/foundry-rs/forge-std.git#f73c73d2018eb6a111f35e4dae7b4f27401e9421" @@ -2858,9 +2854,9 @@ progress@^2.0.0: integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== q@^1.5.1: version "1.5.1" @@ -3787,9 +3783,9 @@ universalify@^0.1.0: integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== uri-js@^4.2.2: version "4.4.1" @@ -3916,9 +3912,9 @@ yallist@^4.0.0: integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@^2.2.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.3.tgz#01f6d18ef036446340007db8e016810e5d64aad9" - integrity sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ== + version "2.3.4" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" + integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" From 4c2e6667837410d5819d74ff76c5d972bf516585 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:19:12 +0400 Subject: [PATCH 3/6] perf: optimize `ArbitratorModule` --- .../modules/resolution/arbitrator_module.md | 2 +- .../modules/resolution/ArbitratorModule.sol | 131 ++-- .../modules/resolution/IArbitratorModule.sol | 164 +++-- .../modules/resolution/ArbitratorModule.t.sol | 609 ++++++++---------- 4 files changed, 444 insertions(+), 462 deletions(-) diff --git a/docs/src/content/modules/resolution/arbitrator_module.md b/docs/src/content/modules/resolution/arbitrator_module.md index b0476171..dac2c04c 100644 --- a/docs/src/content/modules/resolution/arbitrator_module.md +++ b/docs/src/content/modules/resolution/arbitrator_module.md @@ -14,7 +14,7 @@ The Arbitrator Module is a part of the dispute resolution system. It allows an e - `isValid(bytes32 _disputeId)`: Indicates whether the dispute has been arbitrated. - `startResolution(bytes32 _disputeId)`: Starts the arbitration process by calling `resolve` on the arbitrator and flags the dispute as `Active`. - `resolveDispute(bytes32 _disputeId)`: Resolves the dispute by getting the answer from the arbitrator and notifying the oracle. -- `decodeRequestData(bytes32 _requestId)`: Returns the decoded data for a request. +- `decodeRequestData(bytes calldata _data)`: Returns the decoded data for a request. ### Request Parameters diff --git a/solidity/contracts/modules/resolution/ArbitratorModule.sol b/solidity/contracts/modules/resolution/ArbitratorModule.sol index daf419fc..0f4df46a 100644 --- a/solidity/contracts/modules/resolution/ArbitratorModule.sol +++ b/solidity/contracts/modules/resolution/ArbitratorModule.sol @@ -1,62 +1,69 @@ -// // 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 {IArbitratorModule} from '../../../interfaces/modules/resolution/IArbitratorModule.sol'; -// import {IArbitrator} from '../../../interfaces/IArbitrator.sol'; - -// contract ArbitratorModule is Module, IArbitratorModule { -// /** -// * @notice The status of all disputes -// */ -// mapping(bytes32 _disputeId => ArbitrationStatus _status) internal _disputeData; - -// constructor(IOracle _oracle) Module(_oracle) {} - -// /// @inheritdoc IModule -// function moduleName() external pure returns (string memory _moduleName) { -// return 'ArbitratorModule'; -// } - -// /// @inheritdoc IArbitratorModule -// function decodeRequestData(bytes32 _requestId) public view returns (address _arbitrator) { -// _arbitrator = abi.decode(requestData[_requestId], (address)); -// } - -// /// @inheritdoc IArbitratorModule -// function getStatus(bytes32 _disputeId) external view returns (ArbitrationStatus _disputeStatus) { -// _disputeStatus = _disputeData[_disputeId]; -// } - -// /// @inheritdoc IArbitratorModule -// function startResolution(bytes32 _disputeId) external onlyOracle { -// IOracle.Dispute memory _dispute = ORACLE.getDispute(_disputeId); - -// address _arbitrator = abi.decode(requestData[_dispute.requestId], (address)); -// if (_arbitrator == address(0)) revert ArbitratorModule_InvalidArbitrator(); - -// _disputeData[_disputeId] = ArbitrationStatus.Active; -// IArbitrator(_arbitrator).resolve(_disputeId); - -// emit ResolutionStarted(_dispute.requestId, _disputeId); -// } - -// /// @inheritdoc IArbitratorModule -// function resolveDispute(bytes32 _disputeId) external onlyOracle { -// IOracle.Dispute memory _dispute = ORACLE.getDispute(_disputeId); -// if (_dispute.status != IOracle.DisputeStatus.Escalated) revert ArbitratorModule_InvalidDisputeId(); - -// address _arbitrator = abi.decode(requestData[_dispute.requestId], (address)); -// IOracle.DisputeStatus _status = IArbitrator(_arbitrator).getAnswer(_disputeId); - -// if (_status <= IOracle.DisputeStatus.Escalated) revert ArbitratorModule_InvalidResolutionStatus(); -// _disputeData[_disputeId] = ArbitrationStatus.Resolved; - -// ORACLE.updateDisputeStatus(_disputeId, _status); - -// emit DisputeResolved(_dispute.requestId, _disputeId, _status); -// } -// } +// 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 {IArbitratorModule} from '../../../interfaces/modules/resolution/IArbitratorModule.sol'; +import {IArbitrator} from '../../../interfaces/IArbitrator.sol'; + +contract ArbitratorModule is Module, IArbitratorModule { + /** + * @notice The status of all disputes + */ + mapping(bytes32 _disputeId => ArbitrationStatus _status) internal _disputeData; + + constructor(IOracle _oracle) Module(_oracle) {} + + /// @inheritdoc IModule + function moduleName() external pure returns (string memory _moduleName) { + return 'ArbitratorModule'; + } + + /// @inheritdoc IArbitratorModule + function decodeRequestData(bytes calldata _data) public pure returns (RequestParameters memory _params) { + _params = abi.decode(_data, (RequestParameters)); + } + + /// @inheritdoc IArbitratorModule + function getStatus(bytes32 _disputeId) external view returns (ArbitrationStatus _disputeStatus) { + _disputeStatus = _disputeData[_disputeId]; + } + + /// @inheritdoc IArbitratorModule + function startResolution( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external onlyOracle { + RequestParameters memory _params = decodeRequestData(_request.resolutionModuleData); + if (_params.arbitrator == address(0)) revert ArbitratorModule_InvalidArbitrator(); + + _disputeData[_disputeId] = ArbitrationStatus.Active; + IArbitrator(_params.arbitrator).resolve(_disputeId); + + emit ResolutionStarted(_dispute.requestId, _disputeId); + } + + /// @inheritdoc IArbitratorModule + function resolveDispute( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external onlyOracle { + if (ORACLE.disputeStatus(_disputeId) != IOracle.DisputeStatus.Escalated) revert ArbitratorModule_InvalidDisputeId(); + + RequestParameters memory _params = decodeRequestData(_request.resolutionModuleData); + IOracle.DisputeStatus _status = IArbitrator(_params.arbitrator).getAnswer(_disputeId); + + if (_status <= IOracle.DisputeStatus.Escalated) revert ArbitratorModule_InvalidResolutionStatus(); + _disputeData[_disputeId] = ArbitrationStatus.Resolved; + + ORACLE.updateDisputeStatus(_request, _response, _dispute, _status); + + emit DisputeResolved(_dispute.requestId, _disputeId, _status); + } +} diff --git a/solidity/interfaces/modules/resolution/IArbitratorModule.sol b/solidity/interfaces/modules/resolution/IArbitratorModule.sol index b5bf211b..17e9747d 100644 --- a/solidity/interfaces/modules/resolution/IArbitratorModule.sol +++ b/solidity/interfaces/modules/resolution/IArbitratorModule.sol @@ -1,84 +1,106 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; -// import {IResolutionModule} from -// '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/resolution/IResolutionModule.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; +import {IResolutionModule} from + '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/resolution/IResolutionModule.sol'; -// /* -// * @title ArbitratorModule -// * @notice Module allowing an external arbitrator contract -// * to resolve a dispute. -// */ -// interface IArbitratorModule is IResolutionModule { -// /*/////////////////////////////////////////////////////////////// -// ERRORS -// //////////////////////////////////////////////////////////////*/ +/* + * @title ArbitratorModule + * @notice Module allowing an external arbitrator contract + * to resolve a dispute. + */ +interface IArbitratorModule is IResolutionModule { + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Thrown when an unauthorized caller calls a function only the arbitrator can call -// */ -// error ArbitratorModule_OnlyArbitrator(); + /** + * @notice Thrown when an unauthorized caller calls a function only the arbitrator can call + */ + error ArbitratorModule_OnlyArbitrator(); -// /** -// * @notice Thrown when trying to resolve a dispute that is not escalated -// */ -// error ArbitratorModule_InvalidDisputeId(); + /** + * @notice Thrown when trying to resolve a dispute that is not escalated + */ + error ArbitratorModule_InvalidDisputeId(); -// /** -// * @notice Thrown when the arbitrator address is the address zero -// */ -// error ArbitratorModule_InvalidArbitrator(); + /** + * @notice Thrown when the arbitrator address is the address zero + */ + error ArbitratorModule_InvalidArbitrator(); -// /** -// * @notice Thrown when the arbitrator returns an invalid resolution status -// */ -// error ArbitratorModule_InvalidResolutionStatus(); + /** + * @notice Thrown when the arbitrator returns an invalid resolution status + */ + error ArbitratorModule_InvalidResolutionStatus(); -// /*/////////////////////////////////////////////////////////////// -// ENUMS -// //////////////////////////////////////////////////////////////*/ + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ + /** + * @notice Parameters of the request as stored in the module + * @param arbitrator The address of the arbitrator + */ + struct RequestParameters { + address arbitrator; + } -// /** -// * @notice Available status of the arbitration process -// */ -// enum ArbitrationStatus { -// Unknown, // The arbitration process has not started (default) -// Active, // The arbitration process is active -// Resolved // The arbitration process is resolved -// } + /*/////////////////////////////////////////////////////////////// + ENUMS + //////////////////////////////////////////////////////////////*/ -// /*/////////////////////////////////////////////////////////////// -// LOGIC -// //////////////////////////////////////////////////////////////*/ + /** + * @notice Available status of the arbitration process + */ + enum ArbitrationStatus { + Unknown, // The arbitration process has not started (default) + Active, // The arbitration process is active + Resolved // The arbitration process is resolved + } -// /** -// * @notice Returns the current arbitration status of a dispute -// * @param _disputeId The ID of the dispute -// * @return _disputeStatus The `ArbitrationStatus` of the dispute -// */ -// function getStatus(bytes32 _disputeId) external view returns (ArbitrationStatus _disputeStatus); + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Starts the arbitration process by calling `resolve` on the -// * arbitrator and flags the dispute as Active -// * @dev Only callable by the Oracle -// * @dev Will revert if the arbitrator address is the address zero -// * @param _disputeId The ID of the dispute -// */ -// function startResolution(bytes32 _disputeId) external; + /** + * @notice Returns the current arbitration status of a dispute + * @param _disputeId The ID of the dispute + * @return _disputeStatus The `ArbitrationStatus` of the dispute + */ + function getStatus(bytes32 _disputeId) external view returns (ArbitrationStatus _disputeStatus); -// /** -// * @notice Resolves the dispute by getting the answer from the arbitrator -// * and updating the dispute status -// * @dev Only callable by the Oracle -// * @param _disputeId The ID of the dispute -// */ -// function resolveDispute(bytes32 _disputeId) external; + /** + * @notice Starts the arbitration process by calling `resolve` on the + * arbitrator and flags the dispute as Active + * @dev Only callable by the Oracle + * @dev Will revert if the arbitrator address is the address zero + * @param _disputeId The ID of the dispute + */ + function startResolution( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external; -// /** -// * @notice Returns the decoded data for a request -// * @param _requestId The ID of the request -// * @return _arbitrator The address of the arbitrator -// */ -// function decodeRequestData(bytes32 _requestId) external view returns (address _arbitrator); -// } + /** + * @notice Resolves the dispute by getting the answer from the arbitrator + * and updating the dispute status + * @dev Only callable by the Oracle + * @param _disputeId The ID of the dispute + */ + function resolveDispute( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external; + + /** + * @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); +} diff --git a/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol b/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol index 7f13db93..66584655 100644 --- a/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol +++ b/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol @@ -1,328 +1,281 @@ -// // 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 { -// ArbitratorModule, -// IArbitratorModule, -// IArbitrator -// } from '../../../../contracts/modules/resolution/ArbitratorModule.sol'; - -// /** -// * @dev Harness to set an entry in the requestData mapping, without triggering setup request hooks -// */ -// contract ForTest_ArbitratorModule is ArbitratorModule { -// constructor(IOracle _oracle) ArbitratorModule(_oracle) {} - -// function forTest_setRequestData(bytes32 _requestId, bytes memory _data) public { -// requestData[_requestId] = _data; -// } - -// function forTest_setDisputeStatus(bytes32 _disputeId, IArbitratorModule.ArbitrationStatus _status) public { -// _disputeData[_disputeId] = _status; -// } -// } - -// /** -// * @title Arbitrator Module Unit tests -// */ -// contract BaseTest is Test, Helpers { -// // The target contract -// ForTest_ArbitratorModule public arbitratorModule; -// // A mock oracle -// IOracle public oracle; -// // A mock arbitrator -// IArbitrator public arbitrator; -// // Create a new dummy dispute -// IOracle.Dispute public mockDispute; - -// event ResolutionStarted(bytes32 indexed _requestId, bytes32 indexed _disputeId); -// event DisputeResolved(bytes32 indexed _requestId, bytes32 indexed _disputeId, IOracle.DisputeStatus _status); - -// /** -// * @notice Deploy the target and mock oracle -// */ -// function setUp() public { -// oracle = IOracle(makeAddr('Oracle')); -// vm.etch(address(oracle), hex'069420'); - -// arbitrator = IArbitrator(makeAddr('MockArbitrator')); -// vm.etch(address(arbitrator), hex'069420'); - -// arbitratorModule = new ForTest_ArbitratorModule(oracle); - -// mockDispute = IOracle.Dispute({ -// createdAt: block.timestamp, -// disputer: makeAddr('disputer'), -// proposer: makeAddr('proposer'), -// responseId: bytes32('69'), -// requestId: bytes32('69'), -// status: IOracle.DisputeStatus.Active -// }); -// } -// } - -// contract ArbitratorModule_Unit_ModuleData is BaseTest { -// /** -// * @notice Test that the moduleName function returns the correct name -// */ -// function test_moduleNameReturnsName() public { -// assertEq(arbitratorModule.moduleName(), 'ArbitratorModule'); -// } -// /** -// * @notice Test that the decodeRequestData function returns the correct values -// */ - -// function test_decodeRequestData(bytes32 _requestId, address _arbitrator) public { -// // Mock data -// bytes memory _requestData = abi.encode(address(_arbitrator)); - -// // Store the mock dispute -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Test: decode the given request data -// (address _arbitratorStored) = arbitratorModule.decodeRequestData(_requestId); - -// // Check: decoded values match original values? -// assertEq(_arbitratorStored, _arbitrator); -// } - -// /** -// * @notice Test that the status is correctly retrieved -// */ -// function test_getStatus(uint256 _status, bytes32 _disputeId) public { -// _status = bound(_status, 0, uint256(IArbitratorModule.ArbitrationStatus.Resolved)); -// IArbitratorModule.ArbitrationStatus _arbitratorStatus = IArbitratorModule.ArbitrationStatus(_status); - -// // Store the mock dispute -// arbitratorModule.forTest_setDisputeStatus(_disputeId, _arbitratorStatus); - -// // Check: The correct status is returned? -// assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(_status)); -// } -// } - -// contract ArbitratorModule_Unit_StartResolution is BaseTest { -// /** -// * @notice Test that the escalate function works as expected -// */ -// function test_startResolution(bytes32 _disputeId, bytes32 _requestId) public { -// // Mock and expect the dummy dispute -// mockDispute.requestId = _requestId; -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Store the requestData -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock and expect the callback to the arbitrator -// _mockAndExpect(address(arbitrator), abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); - -// vm.prank(address(oracle)); -// arbitratorModule.startResolution(_disputeId); - -// // Check: is status now Escalated? -// assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(IArbitratorModule.ArbitrationStatus.Active)); -// } - -// function test_emitsEvent(bytes32 _disputeId, bytes32 _requestId) public { -// // Mock and expect the dummy dispute -// mockDispute.requestId = _requestId; -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Store the requestData -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock and expect the callback to the arbitrator -// _mockAndExpect(address(arbitrator), abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(arbitratorModule)); -// emit ResolutionStarted(_requestId, _disputeId); - -// vm.prank(address(oracle)); -// arbitratorModule.startResolution(_disputeId); -// } - -// function test_revertInvalidCaller(address _caller, bytes32 _disputeId) public { -// vm.assume(_caller != address(oracle)); - -// // Check: does it revert if the caller is not the Oracle? -// vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); - -// vm.prank(_caller); -// arbitratorModule.startResolution(_disputeId); -// } - -// function test_revertIfEmptyArbitrator(bytes32 _disputeId, bytes32 _requestId) public { -// // Mock and expect the dummy dispute -// mockDispute.requestId = _requestId; -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Store the requestData -// bytes memory _requestData = abi.encode(address(0)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Check: revert? -// vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidArbitrator.selector)); - -// // Test: escalate the dispute -// vm.prank(address(oracle)); -// arbitratorModule.startResolution(_disputeId); -// } -// } - -// contract ArbitratorModule_Unit_ResolveDispute is BaseTest { -// /** -// * @notice Test that the resolve function works as expected -// */ -// function test_resolveDispute(bytes32 _disputeId, bytes32 _requestId, uint256 _status) public { -// vm.assume(_status <= uint256(IOracle.DisputeStatus.Lost)); -// vm.assume(_status > uint256(IOracle.DisputeStatus.Escalated)); -// IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); - -// // Store the mock dispute -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock the dummy dispute -// mockDispute.requestId = _requestId; -// mockDispute.status = IOracle.DisputeStatus.Escalated; - -// // Mock and expect IOracle.getDispute to be called -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Mock and expect getAnswer to be called on the arbitrator -// _mockAndExpect( -// address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) -// ); - -// // Mock and expect IOracle.updateDisputeStatus to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(oracle.updateDisputeStatus, (_disputeId, _arbitratorStatus)), abi.encode() -// ); - -// vm.prank(address(oracle)); -// arbitratorModule.resolveDispute(_disputeId); - -// // Check: is status now Resolved? -// assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(IArbitratorModule.ArbitrationStatus.Resolved)); -// } - -// function test_revertsIfInvalidResolveStatus(bytes32 _disputeId, bytes32 _requestId, uint256 _status) public { -// vm.assume(_status <= uint256(IOracle.DisputeStatus.Escalated)); -// IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); - -// // Store the mock dispute -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock the dummy dispute -// mockDispute.requestId = _requestId; -// mockDispute.status = IOracle.DisputeStatus.Escalated; - -// // Mock and expect IOracle.getDispute to be called -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Mock and expect getAnswer to be called on the arbitrator -// _mockAndExpect( -// address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) -// ); - -// // Check: does it revert if the resolution status is invalid? -// vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidResolutionStatus.selector)); - -// vm.prank(address(oracle)); -// arbitratorModule.resolveDispute(_disputeId); -// } - -// function test_emitsEvent(bytes32 _disputeId, bytes32 _requestId, uint256 _status) public { -// vm.assume(_status <= uint256(IOracle.DisputeStatus.Lost)); -// vm.assume(_status > uint256(IOracle.DisputeStatus.Escalated)); -// IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); - -// // Store the mock dispute -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock the dummy dispute -// mockDispute.requestId = _requestId; -// mockDispute.status = IOracle.DisputeStatus.Escalated; - -// // Mock and expect IOracle.getDispute to be called -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Mock and expect getAnswer to be called on the arbitrator -// _mockAndExpect( -// address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) -// ); - -// // Mock and expect IOracle.updateDisputeStatus to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(oracle.updateDisputeStatus, (_disputeId, _arbitratorStatus)), abi.encode() -// ); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(arbitratorModule)); -// emit DisputeResolved(_requestId, _disputeId, _arbitratorStatus); - -// vm.prank(address(oracle)); -// arbitratorModule.resolveDispute(_disputeId); -// } - -// /** -// * @notice resolve dispute reverts if the dispute status isn't Active -// */ -// function test_revertIfInvalidDispute(bytes32 _requestId, bytes32 _responseId, bytes32 _disputeId) public { -// // Store the requestData -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Test the 3 different invalid status (None, Won, Lost) -// for (uint256 _status; _status < uint256(type(IOracle.DisputeStatus).max); _status++) { -// if (IOracle.DisputeStatus(_status) == IOracle.DisputeStatus.Escalated) continue; -// // Create a new dummy dispute -// IOracle.Dispute memory _dispute = IOracle.Dispute({ -// createdAt: block.timestamp, -// disputer: makeAddr('disputer'), -// proposer: makeAddr('proposer'), -// responseId: _responseId, -// requestId: _requestId, -// status: IOracle.DisputeStatus(_status) -// }); - -// // Mock and expect IOracle.getDispute to be called -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(_dispute)); - -// // Check: does it revert if the dispute id is invalid? -// vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidDisputeId.selector)); - -// vm.prank(address(oracle)); -// arbitratorModule.resolveDispute(_disputeId); -// } -// } - -// /** -// * @notice Test that the resolve function reverts if the caller isn't the arbitrator -// */ -// function test_revertIfWrongSender(bytes32 _disputeId, bytes32 _requestId, address _caller) public { -// vm.assume(_caller != address(oracle)); - -// // Store the mock dispute -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Check: does it revert if not called by the Oracle? -// vm.expectRevert(IModule.Module_OnlyOracle.selector); - -// vm.prank(_caller); -// arbitratorModule.resolveDispute(_disputeId); -// } -// } +// 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 { + ArbitratorModule, + IArbitratorModule, + IArbitrator +} from '../../../../contracts/modules/resolution/ArbitratorModule.sol'; + +/** + * @dev Harness to set an entry in the requestData mapping, without triggering setup request hooks + */ +contract ForTest_ArbitratorModule is ArbitratorModule { + constructor(IOracle _oracle) ArbitratorModule(_oracle) {} + + function forTest_setDisputeStatus(bytes32 _disputeId, IArbitratorModule.ArbitrationStatus _status) public { + _disputeData[_disputeId] = _status; + } +} + +/** + * @title Arbitrator Module Unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + ForTest_ArbitratorModule public arbitratorModule; + // A mock oracle + IOracle public oracle; + // A mock arbitrator + IArbitrator public arbitrator; + // Create a new dummy dispute + IOracle.Dispute public mockDispute; + // Create a new dummy response + IOracle.Response public mockResponse; + address internal _proposer = makeAddr('proposer'); + bytes32 public mockId = bytes32('69'); + + event ResolutionStarted(bytes32 indexed _requestId, bytes32 indexed _disputeId); + event DisputeResolved(bytes32 indexed _requestId, bytes32 indexed _disputeId, IOracle.DisputeStatus _status); + + /** + * @notice Deploy the target and mock oracle + */ + function setUp() public { + oracle = IOracle(makeAddr('Oracle')); + vm.etch(address(oracle), hex'069420'); + + arbitrator = IArbitrator(makeAddr('MockArbitrator')); + vm.etch(address(arbitrator), hex'069420'); + + arbitratorModule = new ForTest_ArbitratorModule(oracle); + + mockDispute = IOracle.Dispute({ + disputer: makeAddr('disputer'), + proposer: makeAddr('proposer'), + responseId: bytes32('69'), + requestId: bytes32('69') + }); + + mockResponse = IOracle.Response({proposer: _proposer, requestId: mockId, response: bytes('')}); + } +} + +contract ArbitratorModule_Unit_ModuleData is BaseTest { + /** + * @notice Test that the moduleName function returns the correct name + */ + function test_moduleNameReturnsName() public { + assertEq(arbitratorModule.moduleName(), 'ArbitratorModule'); + } + /** + * @notice Test that the decodeRequestData function returns the correct values + */ + + function test_decodeRequestData(address _arbitrator) public { + // Mock data + bytes memory _requestData = abi.encode(address(_arbitrator)); + + // Test: decode the given request data + IArbitratorModule.RequestParameters memory _requestParameters = arbitratorModule.decodeRequestData(_requestData); + + // Check: decoded values match original values? + assertEq(_requestParameters.arbitrator, _arbitrator); + } + + /** + * @notice Test that the status is correctly retrieved + */ + function test_getStatus(uint256 _status, bytes32 _disputeId) public { + _status = bound(_status, 0, uint256(IArbitratorModule.ArbitrationStatus.Resolved)); + IArbitratorModule.ArbitrationStatus _arbitratorStatus = IArbitratorModule.ArbitrationStatus(_status); + + // Store the mock dispute + arbitratorModule.forTest_setDisputeStatus(_disputeId, _arbitratorStatus); + + // Check: The correct status is returned? + assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(_status)); + } +} + +contract ArbitratorModule_Unit_StartResolution is BaseTest { + /** + * @notice Test that the escalate function works as expected + */ + function test_startResolution(bytes32 _disputeId, IOracle.Request calldata _request) public { + // Mock and expect the dummy dispute + mockDispute.requestId = _getId(_request); + + // Store the requestData + bytes memory _requestData = abi.encode(address(arbitrator)); + + // Mock and expect the callback to the arbitrator + _mockAndExpect(address(arbitrator), abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); + + vm.prank(address(oracle)); + arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + + // Check: is status now Escalated? + assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(IArbitratorModule.ArbitrationStatus.Active)); + } + + function test_emitsEvent(bytes32 _disputeId, IOracle.Request calldata _request) public { + // Mock and expect the dummy dispute + mockDispute.requestId = _getId(_request); + + // Store the requestData + bytes memory _requestData = abi.encode(address(arbitrator)); + + // Mock and expect the callback to the arbitrator + _mockAndExpect(address(arbitrator), abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(arbitratorModule)); + emit ResolutionStarted(mockDispute.requestId, _disputeId); + + vm.prank(address(oracle)); + arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + } + + function test_revertInvalidCaller(address _caller, bytes32 _disputeId, IOracle.Request calldata _request) public { + vm.assume(_caller != address(oracle)); + + // Check: does it revert if the caller is not the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(_caller); + arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + } + + function test_revertIfEmptyArbitrator(bytes32 _disputeId, IOracle.Request calldata _request) public { + // Mock and expect the dummy dispute + mockDispute.requestId = _getId(_request); + + // Store the requestData + bytes memory _requestData = abi.encode(address(0)); + + // Check: revert? + vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidArbitrator.selector)); + + // Test: escalate the dispute + vm.prank(address(oracle)); + arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + } +} + +contract ArbitratorModule_Unit_ResolveDispute is BaseTest { + /** + * @notice Test that the resolve function works as expected + */ + function test_resolveDispute(bytes32 _disputeId, uint256 _status, IOracle.Request calldata _request) public { + vm.assume(_status <= uint256(IOracle.DisputeStatus.Lost)); + vm.assume(_status > uint256(IOracle.DisputeStatus.Escalated)); + IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); + + // Mock and expect getAnswer to be called on the arbitrator + _mockAndExpect( + address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) + ); + + // Mock and expect IOracle.updateDisputeStatus to be called + _mockAndExpect( + address(oracle), + abi.encodeCall(oracle.updateDisputeStatus, (_request, mockResponse, mockDispute, _arbitratorStatus)), + abi.encode() + ); + + vm.prank(address(oracle)); + arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + + // Check: is status now Resolved? + assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(IArbitratorModule.ArbitrationStatus.Resolved)); + } + + function test_revertsIfInvalidResolveStatus( + bytes32 _disputeId, + uint256 _status, + IOracle.Request calldata _request + ) public { + vm.assume(_status <= uint256(IOracle.DisputeStatus.Escalated)); + IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); + + // Mock and expect getAnswer to be called on the arbitrator + _mockAndExpect( + address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) + ); + + // Check: does it revert if the resolution status is invalid? + vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidResolutionStatus.selector)); + + vm.prank(address(oracle)); + arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + } + + function test_emitsEvent(bytes32 _disputeId, uint256 _status, IOracle.Request calldata _request) public { + vm.assume(_status <= uint256(IOracle.DisputeStatus.Lost)); + vm.assume(_status > uint256(IOracle.DisputeStatus.Escalated)); + IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); + + // Mock and expect getAnswer to be called on the arbitrator + _mockAndExpect( + address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) + ); + + // Mock and expect IOracle.updateDisputeStatus to be called + _mockAndExpect( + address(oracle), + abi.encodeCall(oracle.updateDisputeStatus, (_request, mockResponse, mockDispute, _arbitratorStatus)), + abi.encode() + ); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(arbitratorModule)); + emit DisputeResolved(_getId(_request), _disputeId, _arbitratorStatus); + + vm.prank(address(oracle)); + arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + } + + /** + * @notice resolve dispute reverts if the dispute status isn't Active + */ + function test_revertIfInvalidDispute(IOracle.Request calldata _request, bytes32 _disputeId) public { + // Test the 3 different invalid status (None, Won, Lost) + for (uint256 _status; _status < uint256(type(IOracle.DisputeStatus).max); _status++) { + if (IOracle.DisputeStatus(_status) == IOracle.DisputeStatus.Escalated) continue; + // Create a new dummy dispute + IOracle.Dispute memory _dispute = IOracle.Dispute({ + disputer: makeAddr('disputer'), + proposer: makeAddr('proposer'), + responseId: _getId(mockResponse), + requestId: _getId(_request) + }); + + // Check: does it revert if the dispute id is invalid? + vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidDisputeId.selector)); + + vm.prank(address(oracle)); + arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + } + } + + /** + * @notice Test that the resolve function reverts if the caller isn't the arbitrator + */ + function test_revertIfWrongSender(bytes32 _disputeId, address _caller, IOracle.Request calldata _request) public { + vm.assume(_caller != address(oracle)); + + // Check: does it revert if not called by the Oracle? + vm.expectRevert(IModule.Module_OnlyOracle.selector); + + vm.prank(_caller); + arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + } +} From 6ce0907148b5e1a1efe7e4334d388ff9f25ce40c Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sat, 11 Nov 2023 16:52:22 +0400 Subject: [PATCH 4/6] docs: natspec and documentation --- .../modules/resolution/arbitrator_module.md | 10 ++--- .../modules/resolution/IArbitratorModule.sol | 42 ++++++++++--------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/src/content/modules/resolution/arbitrator_module.md b/docs/src/content/modules/resolution/arbitrator_module.md index dac2c04c..a46327f6 100644 --- a/docs/src/content/modules/resolution/arbitrator_module.md +++ b/docs/src/content/modules/resolution/arbitrator_module.md @@ -10,11 +10,11 @@ The Arbitrator Module is a part of the dispute resolution system. It allows an e ### Key Methods -- `getStatus(bytes32 _disputeId)`: Returns the arbitration status of a dispute. -- `isValid(bytes32 _disputeId)`: Indicates whether the dispute has been arbitrated. -- `startResolution(bytes32 _disputeId)`: Starts the arbitration process by calling `resolve` on the arbitrator and flags the dispute as `Active`. -- `resolveDispute(bytes32 _disputeId)`: Resolves the dispute by getting the answer from the arbitrator and notifying the oracle. -- `decodeRequestData(bytes calldata _data)`: Returns the decoded data for a request. +- `getStatus`: Returns the arbitration status of a dispute. +- `isValid`: Indicates whether the dispute has been arbitrated. +- `startResolution`: Starts the arbitration process by calling `resolve` on the arbitrator and flags the dispute as `Active`. +- `resolveDispute`: Resolves the dispute by getting the answer from the arbitrator and notifying the oracle. +- `decodeRequestData`: Returns the decoded data for a request. ### Request Parameters diff --git a/solidity/interfaces/modules/resolution/IArbitratorModule.sol b/solidity/interfaces/modules/resolution/IArbitratorModule.sol index 17e9747d..3582fa54 100644 --- a/solidity/interfaces/modules/resolution/IArbitratorModule.sol +++ b/solidity/interfaces/modules/resolution/IArbitratorModule.sol @@ -7,19 +7,13 @@ import {IResolutionModule} from /* * @title ArbitratorModule - * @notice Module allowing an external arbitrator contract - * to resolve a dispute. + * @notice Module allowing an external arbitrator contract to resolve a dispute. */ interface IArbitratorModule is IResolutionModule { /*/////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ - /** - * @notice Thrown when an unauthorized caller calls a function only the arbitrator can call - */ - error ArbitratorModule_OnlyArbitrator(); - /** * @notice Thrown when trying to resolve a dispute that is not escalated */ @@ -63,19 +57,31 @@ interface IArbitratorModule is IResolutionModule { 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 Returns the current arbitration status of a dispute + * * @param _disputeId The ID of the dispute * @return _disputeStatus The `ArbitrationStatus` of the dispute */ function getStatus(bytes32 _disputeId) external view returns (ArbitrationStatus _disputeStatus); /** - * @notice Starts the arbitration process by calling `resolve` on the - * arbitrator and flags the dispute as Active + * @notice Starts the arbitration process by calling `resolve` on the arbitrator and flags the dispute as Active + * * @dev Only callable by the Oracle * @dev Will revert if the arbitrator address is the address zero - * @param _disputeId The ID of the dispute + * @param _disputeId The ID of the dispute + * @param _request The request + * @param _response The disputed response + * @param _dispute The dispute being sent to the resolution */ function startResolution( bytes32 _disputeId, @@ -85,10 +91,13 @@ interface IArbitratorModule is IResolutionModule { ) external; /** - * @notice Resolves the dispute by getting the answer from the arbitrator - * and updating the dispute status + * @notice Resolves the dispute by getting the answer from the arbitrator and updating the dispute status + * * @dev Only callable by the Oracle - * @param _disputeId The ID of the dispute + * @param _disputeId The ID of the dispute + * @param _request The request + * @param _response The disputed response + * @param _dispute The dispute that is being resolved */ function resolveDispute( bytes32 _disputeId, @@ -96,11 +105,4 @@ interface IArbitratorModule is IResolutionModule { IOracle.Response calldata _response, IOracle.Dispute calldata _dispute ) external; - - /** - * @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); } From da99cdd55d406516eadd576f131b93452f3d60a5 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sat, 11 Nov 2023 16:53:43 +0400 Subject: [PATCH 5/6] test: fix unit tests --- .../modules/resolution/ArbitratorModule.t.sol | 147 +++++++++--------- 1 file changed, 76 insertions(+), 71 deletions(-) diff --git a/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol b/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol index 66584655..eca711aa 100644 --- a/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol +++ b/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol @@ -35,13 +35,8 @@ contract BaseTest is Test, Helpers { IOracle public oracle; // A mock arbitrator IArbitrator public arbitrator; - // Create a new dummy dispute - IOracle.Dispute public mockDispute; - // Create a new dummy response - IOracle.Response public mockResponse; - address internal _proposer = makeAddr('proposer'); - bytes32 public mockId = bytes32('69'); + // Events event ResolutionStarted(bytes32 indexed _requestId, bytes32 indexed _disputeId); event DisputeResolved(bytes32 indexed _requestId, bytes32 indexed _disputeId, IOracle.DisputeStatus _status); @@ -56,15 +51,6 @@ contract BaseTest is Test, Helpers { vm.etch(address(arbitrator), hex'069420'); arbitratorModule = new ForTest_ArbitratorModule(oracle); - - mockDispute = IOracle.Dispute({ - disputer: makeAddr('disputer'), - proposer: makeAddr('proposer'), - responseId: bytes32('69'), - requestId: bytes32('69') - }); - - mockResponse = IOracle.Response({proposer: _proposer, requestId: mockId, response: bytes('')}); } } @@ -81,7 +67,7 @@ contract ArbitratorModule_Unit_ModuleData is BaseTest { function test_decodeRequestData(address _arbitrator) public { // Mock data - bytes memory _requestData = abi.encode(address(_arbitrator)); + bytes memory _requestData = abi.encode(_arbitrator); // Test: decode the given request data IArbitratorModule.RequestParameters memory _requestParameters = arbitratorModule.decodeRequestData(_requestData); @@ -109,64 +95,64 @@ contract ArbitratorModule_Unit_StartResolution is BaseTest { /** * @notice Test that the escalate function works as expected */ - function test_startResolution(bytes32 _disputeId, IOracle.Request calldata _request) public { - // Mock and expect the dummy dispute - mockDispute.requestId = _getId(_request); + function test_startResolution(address _arbitrator) public assumeFuzzable(_arbitrator) { + mockRequest.resolutionModuleData = abi.encode(_arbitrator); + bytes32 _requestId = _getId(mockRequest); - // Store the requestData - bytes memory _requestData = abi.encode(address(arbitrator)); + mockResponse.requestId = _requestId; + mockDispute.requestId = _requestId; + bytes32 _disputeId = _getId(mockDispute); // Mock and expect the callback to the arbitrator - _mockAndExpect(address(arbitrator), abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); + _mockAndExpect(_arbitrator, abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); vm.prank(address(oracle)); - arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + arbitratorModule.startResolution(_disputeId, mockRequest, mockResponse, mockDispute); // Check: is status now Escalated? assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(IArbitratorModule.ArbitrationStatus.Active)); } - function test_emitsEvent(bytes32 _disputeId, IOracle.Request calldata _request) public { - // Mock and expect the dummy dispute - mockDispute.requestId = _getId(_request); - - // Store the requestData - bytes memory _requestData = abi.encode(address(arbitrator)); + function test_emitsEvent(address _arbitrator) public assumeFuzzable(_arbitrator) { + mockRequest.resolutionModuleData = abi.encode(_arbitrator); + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + mockDispute.requestId = _requestId; + bytes32 _disputeId = _getId(mockDispute); // Mock and expect the callback to the arbitrator - _mockAndExpect(address(arbitrator), abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); + _mockAndExpect(_arbitrator, abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); // Check: is the event emitted? vm.expectEmit(true, true, true, true, address(arbitratorModule)); emit ResolutionStarted(mockDispute.requestId, _disputeId); vm.prank(address(oracle)); - arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + arbitratorModule.startResolution(_disputeId, mockRequest, mockResponse, mockDispute); } - function test_revertInvalidCaller(address _caller, bytes32 _disputeId, IOracle.Request calldata _request) public { + function test_revertInvalidCaller(address _caller) public { vm.assume(_caller != address(oracle)); // Check: does it revert if the caller is not the Oracle? vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); vm.prank(_caller); - arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + arbitratorModule.startResolution(_getId(mockDispute), mockRequest, mockResponse, mockDispute); } - function test_revertIfEmptyArbitrator(bytes32 _disputeId, IOracle.Request calldata _request) public { - // Mock and expect the dummy dispute - mockDispute.requestId = _getId(_request); - - // Store the requestData - bytes memory _requestData = abi.encode(address(0)); + function test_revertIfEmptyArbitrator() public { + mockRequest.resolutionModuleData = abi.encode(address(0)); + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + mockDispute.requestId = _requestId; // Check: revert? vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidArbitrator.selector)); // Test: escalate the dispute vm.prank(address(oracle)); - arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + arbitratorModule.startResolution(_getId(mockDispute), mockRequest, mockResponse, mockDispute); } } @@ -174,91 +160,110 @@ contract ArbitratorModule_Unit_ResolveDispute is BaseTest { /** * @notice Test that the resolve function works as expected */ - function test_resolveDispute(bytes32 _disputeId, uint256 _status, IOracle.Request calldata _request) public { - vm.assume(_status <= uint256(IOracle.DisputeStatus.Lost)); - vm.assume(_status > uint256(IOracle.DisputeStatus.Escalated)); + function test_resolveDispute(uint256 _status, address _arbitrator) public assumeFuzzable(_arbitrator) { + _status = bound(_status, uint256(IOracle.DisputeStatus.Escalated) + 1, uint256(IOracle.DisputeStatus.Lost)); IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); + mockRequest.resolutionModuleData = abi.encode(_arbitrator); + mockDispute.requestId = _getId(mockRequest); + bytes32 _disputeId = _getId(mockDispute); + + // Mock and expect IOracle.disputeStatus to be called + _mockAndExpect( + address(oracle), abi.encodeCall(oracle.disputeStatus, (_disputeId)), abi.encode(IOracle.DisputeStatus.Escalated) + ); + // Mock and expect getAnswer to be called on the arbitrator _mockAndExpect( - address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) + address(_arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) ); // Mock and expect IOracle.updateDisputeStatus to be called _mockAndExpect( address(oracle), - abi.encodeCall(oracle.updateDisputeStatus, (_request, mockResponse, mockDispute, _arbitratorStatus)), + abi.encodeCall(oracle.updateDisputeStatus, (mockRequest, mockResponse, mockDispute, _arbitratorStatus)), abi.encode() ); vm.prank(address(oracle)); - arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + arbitratorModule.resolveDispute(_disputeId, mockRequest, mockResponse, mockDispute); // Check: is status now Resolved? assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(IArbitratorModule.ArbitrationStatus.Resolved)); } - function test_revertsIfInvalidResolveStatus( - bytes32 _disputeId, - uint256 _status, - IOracle.Request calldata _request - ) public { + function test_revertsIfInvalidResolveStatus(uint256 _status, address _arbitrator) public assumeFuzzable(_arbitrator) { vm.assume(_status <= uint256(IOracle.DisputeStatus.Escalated)); IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); + mockRequest.resolutionModuleData = abi.encode(_arbitrator); + mockDispute.requestId = _getId(mockRequest); + bytes32 _disputeId = _getId(mockDispute); + + // Mock and expect IOracle.disputeStatus to be called + _mockAndExpect( + address(oracle), abi.encodeCall(oracle.disputeStatus, (_disputeId)), abi.encode(IOracle.DisputeStatus.Escalated) + ); + // Mock and expect getAnswer to be called on the arbitrator _mockAndExpect( - address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) + address(_arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) ); // Check: does it revert if the resolution status is invalid? vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidResolutionStatus.selector)); vm.prank(address(oracle)); - arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + arbitratorModule.resolveDispute(_disputeId, mockRequest, mockResponse, mockDispute); } - function test_emitsEvent(bytes32 _disputeId, uint256 _status, IOracle.Request calldata _request) public { + function test_emitsEvent(uint256 _status, address _arbitrator) public assumeFuzzable(_arbitrator) { vm.assume(_status <= uint256(IOracle.DisputeStatus.Lost)); vm.assume(_status > uint256(IOracle.DisputeStatus.Escalated)); IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); - // Mock and expect getAnswer to be called on the arbitrator + mockRequest.resolutionModuleData = abi.encode(_arbitrator); + mockDispute.requestId = _getId(mockRequest); + bytes32 _disputeId = _getId(mockDispute); + + // Mock and expect IOracle.disputeStatus to be called _mockAndExpect( - address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) + address(oracle), abi.encodeCall(oracle.disputeStatus, (_disputeId)), abi.encode(IOracle.DisputeStatus.Escalated) ); + // Mock and expect getAnswer to be called on the arbitrator + _mockAndExpect(_arbitrator, abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus)); + // Mock and expect IOracle.updateDisputeStatus to be called _mockAndExpect( address(oracle), - abi.encodeCall(oracle.updateDisputeStatus, (_request, mockResponse, mockDispute, _arbitratorStatus)), + abi.encodeCall(oracle.updateDisputeStatus, (mockRequest, mockResponse, mockDispute, _arbitratorStatus)), abi.encode() ); // Check: is the event emitted? vm.expectEmit(true, true, true, true, address(arbitratorModule)); - emit DisputeResolved(_getId(_request), _disputeId, _arbitratorStatus); + emit DisputeResolved(_getId(mockRequest), _disputeId, _arbitratorStatus); vm.prank(address(oracle)); - arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + arbitratorModule.resolveDispute(_disputeId, mockRequest, mockResponse, mockDispute); } /** * @notice resolve dispute reverts if the dispute status isn't Active */ - function test_revertIfInvalidDispute(IOracle.Request calldata _request, bytes32 _disputeId) public { + function test_revertIfInvalidDispute(IOracle.Request calldata _request) public { // Test the 3 different invalid status (None, Won, Lost) for (uint256 _status; _status < uint256(type(IOracle.DisputeStatus).max); _status++) { if (IOracle.DisputeStatus(_status) == IOracle.DisputeStatus.Escalated) continue; - // Create a new dummy dispute - IOracle.Dispute memory _dispute = IOracle.Dispute({ - disputer: makeAddr('disputer'), - proposer: makeAddr('proposer'), - responseId: _getId(mockResponse), - requestId: _getId(_request) - }); - - // Check: does it revert if the dispute id is invalid? + mockDispute.requestId = _getId(_request); + bytes32 _disputeId = _getId(mockDispute); + + // Mock and expect IOracle.disputeStatus to be called + _mockAndExpect( + address(oracle), abi.encodeCall(oracle.disputeStatus, (_disputeId)), abi.encode(IOracle.DisputeStatus(_status)) + ); + vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidDisputeId.selector)); vm.prank(address(oracle)); @@ -269,13 +274,13 @@ contract ArbitratorModule_Unit_ResolveDispute is BaseTest { /** * @notice Test that the resolve function reverts if the caller isn't the arbitrator */ - function test_revertIfWrongSender(bytes32 _disputeId, address _caller, IOracle.Request calldata _request) public { + function test_revertIfWrongSender(address _caller) public { vm.assume(_caller != address(oracle)); // Check: does it revert if not called by the Oracle? vm.expectRevert(IModule.Module_OnlyOracle.selector); vm.prank(_caller); - arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + arbitratorModule.resolveDispute(_getId(mockDispute), mockRequest, mockResponse, mockDispute); } } From 2ff94bde241bd9d718421bae5f15bd77c29ad980 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sat, 11 Nov 2023 19:36:36 +0400 Subject: [PATCH 6/6] docs: tiny natspec update --- .../interfaces/modules/resolution/IArbitratorModule.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/solidity/interfaces/modules/resolution/IArbitratorModule.sol b/solidity/interfaces/modules/resolution/IArbitratorModule.sol index 3582fa54..fe78357f 100644 --- a/solidity/interfaces/modules/resolution/IArbitratorModule.sol +++ b/solidity/interfaces/modules/resolution/IArbitratorModule.sol @@ -76,8 +76,8 @@ interface IArbitratorModule is IResolutionModule { /** * @notice Starts the arbitration process by calling `resolve` on the arbitrator and flags the dispute as Active * - * @dev Only callable by the Oracle - * @dev Will revert if the arbitrator address is the address zero + * @dev Only callable by the Oracle + * @dev Will revert if the arbitrator address is the address zero * @param _disputeId The ID of the dispute * @param _request The request * @param _response The disputed response @@ -93,7 +93,7 @@ interface IArbitratorModule is IResolutionModule { /** * @notice Resolves the dispute by getting the answer from the arbitrator and updating the dispute status * - * @dev Only callable by the Oracle + * @dev Only callable by the Oracle * @param _disputeId The ID of the dispute * @param _request The request * @param _response The disputed response