From 312ccfb947ff686aab3ea1c0b8564ebf21413a0f Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Mon, 6 Nov 2023 19:50:33 +0400 Subject: [PATCH 1/7] feat: update `prophet-core` package --- package.json | 2 +- yarn.lock | 60 ++++++++++++++++++++++++---------------------------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index b7700bc1..47b7c797 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-01089b2c", "@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..489c192e 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-01089b2c": + version "0.0.0-01089b2c" + resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-core-contracts/-/prophet-core-contracts-0.0.0-01089b2c.tgz#c5ce2566ee8ae4fd7d613f1705702053b2e74a93" + integrity sha512-mIOKHzO18k0Ja3sZ8XI3JBxewQTPqZlm6YYqikkjCx28ddxZGZyEw2e01dexsVoF5fSkip89Hdb6xcDt4WS3qA== dependencies: "@defi-wonderland/solidity-utils" "0.0.0-3e9c8e8b" "@openzeppelin/contracts" "^4.9.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 202a05ee530eb344bebe31e12523e66baca5ae1c Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Mon, 6 Nov 2023 19:50:51 +0400 Subject: [PATCH 2/7] 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 aa93c6a648910268a9c9122d45cf60611edbb233 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Mon, 6 Nov 2023 19:52:30 +0400 Subject: [PATCH 3/7] perf: optimize `RootVerificationModule` --- .../dispute/root_verification_module.md | 2 +- .../dispute/RootVerificationModule.sol | 190 +++++++++--------- .../dispute/IRootVerificationModule.sol | 137 ++++++------- .../dispute/RootVerificationModule.t.sol | 22 +- 4 files changed, 159 insertions(+), 192 deletions(-) diff --git a/docs/src/content/modules/dispute/root_verification_module.md b/docs/src/content/modules/dispute/root_verification_module.md index 6bb6e1f0..6e6e5fb6 100644 --- a/docs/src/content/modules/dispute/root_verification_module.md +++ b/docs/src/content/modules/dispute/root_verification_module.md @@ -10,7 +10,7 @@ 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. +- `decodeRequestData(bytes calldata _data)`: 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. diff --git a/solidity/contracts/modules/dispute/RootVerificationModule.sol b/solidity/contracts/modules/dispute/RootVerificationModule.sol index e19f1cf0..61f349e0 100644 --- a/solidity/contracts/modules/dispute/RootVerificationModule.sol +++ b/solidity/contracts/modules/dispute/RootVerificationModule.sol @@ -1,94 +1,96 @@ -// // 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) + }); + + ORACLE.proposeResponse(_dispute.disputer, _request, _newResponse); + ORACLE.finalize(_request, _newResponse); + } else { + ORACLE.finalize(_request, _response); + } + + delete _correctRoots[_dispute.requestId]; + + // TODO: Emit event + // emit DisputeStatusChanged({ + // _disputeId: _disputeId, + // _dispute: _dispute, + // _status: IOracle.DisputeStatus.Resolved + // }); + } + + /// @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; + + bool _won = abi.decode(_response.response, (bytes32)) != _correctRoot; + + // TODO: call ORACLE.updateDisputeStatus + emit ResponseDisputed({ + _responseId: _dispute.responseId, + _disputeId: _getId(_dispute), + _dispute: _dispute, + _blockNumber: block.number + }); + } +} diff --git a/solidity/interfaces/modules/dispute/IRootVerificationModule.sol b/solidity/interfaces/modules/dispute/IRootVerificationModule.sol index 682468eb..26177064 100644 --- a/solidity/interfaces/modules/dispute/IRootVerificationModule.sol +++ b/solidity/interfaces/modules/dispute/IRootVerificationModule.sol @@ -1,84 +1,67 @@ -// // 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); + /// @inheritdoc IDisputeModule + 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; -// } + /// @inheritdoc IDisputeModule + function onDisputeStatusChange( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external; +} diff --git a/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol b/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol index 21ec848d..98837592 100644 --- a/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol +++ b/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol @@ -107,12 +107,11 @@ // rootVerificationModule = new ForTest_RootVerificationModule(oracle); // mockDispute = IOracle.Dispute({ -// createdAt: block.timestamp, // disputer: dude, // responseId: mockId, // proposer: dude, // requestId: mockId, -// status: IOracle.DisputeStatus.Active + // }); // } // } @@ -263,7 +262,7 @@ // // Check: is the event emitted? // vm.expectEmit(true, true, true, true, address(rootVerificationModule)); -// emit ResponseDisputed(_requestId, _responseId, _disputer, _proposer); +// emit ResponseDisputed({_requestId: _dispute.requestId, _responseId: _dispute.responseId, _dispute: _dispute, blockNumber: block.number}); // vm.prank(address(oracle)); // rootVerificationModule.disputeResponse(_requestId, _responseId, _disputer, _proposer); @@ -337,20 +336,3 @@ // 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); -// } -// } From 99121e4dd6c920a183a65d9bb37c80485f829087 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:57:38 +0400 Subject: [PATCH 4/7] feat: update `prophet-core` package --- package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 47b7c797..dcb5ecad 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "package.json": "sort-package-json" }, "dependencies": { - "@defi-wonderland/prophet-core-contracts": "0.0.0-01089b2c", + "@defi-wonderland/prophet-core-contracts": "0.0.0-a1d2cc55", "@defi-wonderland/solidity-utils": "0.0.0-3e9c8e8b", "@openzeppelin/contracts": "^4.9.3", "ds-test": "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0", diff --git a/yarn.lock b/yarn.lock index 489c192e..658b1212 100644 --- a/yarn.lock +++ b/yarn.lock @@ -192,10 +192,10 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@defi-wonderland/prophet-core-contracts@0.0.0-01089b2c": - version "0.0.0-01089b2c" - resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-core-contracts/-/prophet-core-contracts-0.0.0-01089b2c.tgz#c5ce2566ee8ae4fd7d613f1705702053b2e74a93" - integrity sha512-mIOKHzO18k0Ja3sZ8XI3JBxewQTPqZlm6YYqikkjCx28ddxZGZyEw2e01dexsVoF5fSkip89Hdb6xcDt4WS3qA== +"@defi-wonderland/prophet-core-contracts@0.0.0-a1d2cc55": + version "0.0.0-a1d2cc55" + resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-core-contracts/-/prophet-core-contracts-0.0.0-a1d2cc55.tgz#e0bba63cdb143ffba6721049d2b0577eb39329fb" + integrity sha512-gl+8QvkzPd144yESzXhl2ceJ5blZczKh7HLioSfJ1uZnrJAR90z/MJD1fCb/1Q3oDyaq6WMWnJSK9VvMkadTtQ== dependencies: "@defi-wonderland/solidity-utils" "0.0.0-3e9c8e8b" "@openzeppelin/contracts" "^4.9.3" @@ -274,9 +274,9 @@ antlr4ts "^0.5.0-alpha.4" "@solidity-parser/parser@^0.16.0": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.1.tgz#f7c8a686974e1536da0105466c4db6727311253c" - integrity sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw== + version "0.16.2" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.2.tgz#42cb1e3d88b3e8029b0c9befff00b634cd92d2fa" + integrity sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg== dependencies: antlr4ts "^0.5.0-alpha.4" @@ -324,9 +324,9 @@ ts-essentials "^7.0.1" "@types/minimist@^1.2.0": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.4.tgz#81f886786411c45bba3f33e781ab48bd56bfca2e" - integrity sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ== + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" + integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== "@types/node@20.5.1": version "20.5.1" @@ -334,9 +334,9 @@ integrity sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg== "@types/normalize-package-data@^2.4.0": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz#291c243e4b94dbfbc0c0ee26b7666f1d5c030e2c" - integrity sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg== + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== "@types/prettier@^2.1.1": version "2.7.3" @@ -1568,7 +1568,7 @@ for-each@^0.3.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" + resolved "git+https://github.com/foundry-rs/forge-std.git#37a37ab73364d6644bfe11edf88a07880f99bd56" "forge-std@git+https://github.com/foundry-rs/forge-std.git#e8a047e3f40f13fa37af6fe14e6e06283d9a060e": version "1.5.6" From 388e99b04106073ce3ac7558f5cb8be7e166153b Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Fri, 10 Nov 2023 17:16:46 +0400 Subject: [PATCH 5/7] feat: update helpers --- solidity/test/utils/Helpers.sol | 70 ++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/solidity/test/utils/Helpers.sol b/solidity/test/utils/Helpers.sol index af32e439..4f22491b 100644 --- a/solidity/test/utils/Helpers.sol +++ b/solidity/test/utils/Helpers.sol @@ -6,37 +6,53 @@ import {DSTestPlus} from '@defi-wonderland/solidity-utils/solidity/test/DSTestPl import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; import {IAccountingExtension} from '../../interfaces/extensions/IAccountingExtension.sol'; +import {TestConstants} from './TestConstants.sol'; + +contract Helpers is DSTestPlus, TestConstants { + // 100% random sequence of bytes representing request, response, or dispute id + bytes32 public mockId = bytes32('69'); + + // Placeholder addresses + address public disputer = makeAddr('disputer'); + address public proposer = makeAddr('proposer'); + + // Mocks objects + IOracle.Request public mockRequest; + IOracle.Response public mockResponse = IOracle.Response({proposer: proposer, requestId: mockId, response: bytes('')}); + IOracle.Dispute public mockDispute = + IOracle.Dispute({disputer: disputer, responseId: mockId, proposer: proposer, requestId: mockId}); + + // Shared events that all modules emit + event RequestFinalized(bytes32 indexed _requestId, IOracle.Response _response, address _finalizer); -contract Helpers is DSTestPlus { modifier assumeFuzzable(address _address) { _assumeFuzzable(_address); _; } + /** + * @notice Ensures that a fuzzed address can be used for deployment and calls + * + * @param _address The address to check + */ function _assumeFuzzable(address _address) internal pure { assumeNotForgeAddress(_address); assumeNotZeroAddress(_address); assumeNotPrecompile(_address); } + /** + * @notice Sets up a mock and expects a call to it + * + * @param _receiver The address to have a mock on + * @param _calldata The calldata to mock and expect + * @param _returned The data to return from the mocked call + */ function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { vm.mockCall(_receiver, _calldata, _returned); vm.expectCall(_receiver, _calldata); } - function _getMockDispute( - bytes32 _requestId, - address _disputer, - address _proposer - ) internal view returns (IOracle.Dispute memory _dispute) { - _dispute = IOracle.Dispute({ - disputer: _disputer, - responseId: bytes32('response'), - proposer: _proposer, - requestId: _requestId - }); - } - function _forBondDepositERC20( IAccountingExtension _accountingExtension, address _depositor, @@ -52,14 +68,32 @@ contract Helpers is DSTestPlus { vm.stopPrank(); } - function _getId(IOracle.Response memory _response) internal pure returns (bytes32 _id) { - _id = keccak256(abi.encode(_response)); - } - + /** + * @notice Computes the ID of a given request as it's done in the Oracle + * + * @param _request The request to compute the ID for + * @return _id The ID of the request + */ function _getId(IOracle.Request memory _request) internal pure returns (bytes32 _id) { _id = keccak256(abi.encode(_request)); } + /** + * @notice Computes the ID of a given response as it's done in the Oracle + * + * @param _response The response to compute the ID for + * @return _id The ID of the response + */ + function _getId(IOracle.Response memory _response) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_response)); + } + + /** + * @notice Computes the ID of a given dispute as it's done in the Oracle + * + * @param _dispute The dispute to compute the ID for + * @return _id The ID of the dispute + */ function _getId(IOracle.Dispute memory _dispute) internal pure returns (bytes32 _id) { _id = keccak256(abi.encode(_dispute)); } From 2866715e801ac2d0f180ea9ba2893b057701b2b1 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:02:42 +0400 Subject: [PATCH 6/7] fix: improve logic in `RootVerificationModule` and fix tests --- .../dispute/RootVerificationModule.sol | 19 +- .../dispute/IRootVerificationModule.sol | 28 +- .../dispute/RootVerificationModule.t.sol | 642 +++++++++--------- 3 files changed, 334 insertions(+), 355 deletions(-) diff --git a/solidity/contracts/modules/dispute/RootVerificationModule.sol b/solidity/contracts/modules/dispute/RootVerificationModule.sol index 61f349e0..e954fc1a 100644 --- a/solidity/contracts/modules/dispute/RootVerificationModule.sol +++ b/solidity/contracts/modules/dispute/RootVerificationModule.sol @@ -56,20 +56,16 @@ contract RootVerificationModule is Module, IRootVerificationModule { response: abi.encode(_correctRoot) }); - ORACLE.proposeResponse(_dispute.disputer, _request, _newResponse); + emit DisputeStatusChanged({_disputeId: _disputeId, _dispute: _dispute, _status: IOracle.DisputeStatus.Won}); + + ORACLE.proposeResponse(_request, _newResponse); ORACLE.finalize(_request, _newResponse); } else { + emit DisputeStatusChanged({_disputeId: _disputeId, _dispute: _dispute, _status: IOracle.DisputeStatus.Lost}); ORACLE.finalize(_request, _response); } delete _correctRoots[_dispute.requestId]; - - // TODO: Emit event - // emit DisputeStatusChanged({ - // _disputeId: _disputeId, - // _dispute: _dispute, - // _status: IOracle.DisputeStatus.Resolved - // }); } /// @inheritdoc IRootVerificationModule @@ -83,14 +79,17 @@ contract RootVerificationModule is Module, IRootVerificationModule { bytes32 _correctRoot = _params.treeVerifier.calculateRoot(_params.treeData, _params.leavesToInsert); _correctRoots[_response.requestId] = _correctRoot; - bool _won = abi.decode(_response.response, (bytes32)) != _correctRoot; + IOracle.DisputeStatus _status = + abi.decode(_response.response, (bytes32)) != _correctRoot ? IOracle.DisputeStatus.Won : IOracle.DisputeStatus.Lost; - // TODO: call ORACLE.updateDisputeStatus emit ResponseDisputed({ + _requestId: _response.requestId, _responseId: _dispute.responseId, _disputeId: _getId(_dispute), _dispute: _dispute, _blockNumber: block.number }); + + ORACLE.updateDisputeStatus(_request, _response, _dispute, _status); } } diff --git a/solidity/interfaces/modules/dispute/IRootVerificationModule.sol b/solidity/interfaces/modules/dispute/IRootVerificationModule.sol index 26177064..f29c92f1 100644 --- a/solidity/interfaces/modules/dispute/IRootVerificationModule.sol +++ b/solidity/interfaces/modules/dispute/IRootVerificationModule.sol @@ -11,11 +11,10 @@ import {IAccountingExtension} from '../../extensions/IAccountingExtension.sol'; /* * @title RootVerificationModule - * @notice Dispute module allowing disputers to calculate the correct root - * for a given request and propose it as a response. If the disputer wins the - * dispute, he is rewarded with the bond of the proposer. - * @dev This module is a pre-dispute module. It allows disputing - * and resolving a response in a single call. + * @notice Dispute module allowing disputers to calculate the correct root for a given request and propose it as a response. + * If the disputer wins the dispute, he is rewarded with the bond of the proposer. + * + * @dev This module is a pre-dispute module. It allows disputing and resolving a response in a single call. */ interface IRootVerificationModule is IDisputeModule { /*/////////////////////////////////////////////////////////////// @@ -50,14 +49,29 @@ interface IRootVerificationModule is IDisputeModule { */ function decodeRequestData(bytes calldata _data) external view returns (RequestParameters memory _params); - /// @inheritdoc IDisputeModule + /** + * @notice Initiates and resolves the dispute by comparing the proposed response with the one returned by the verifier + * + * @dev This function will notify the oracle about the outcome of the dispute + * @param _request The request that the response was proposed to + * @param _response The response that is being disputed + * @param _dispute The dispute created by the oracle + */ function disputeResponse( IOracle.Request calldata _request, IOracle.Response calldata _response, IOracle.Dispute calldata _dispute ) external; - /// @inheritdoc IDisputeModule + /** + * @notice Depending on the status of the dispute, either pays the disputer and submits the correct response, + * or pays the proposer. Finalizes the request in any case. + * + * @param _disputeId The id of the dispute + * @param _request The request that the response was proposed to + * @param _response The response that was disputed + * @param _dispute The dispute + */ function onDisputeStatusChange( bytes32 _disputeId, IOracle.Request calldata _request, diff --git a/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol b/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol index 98837592..7959bd10 100644 --- a/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol +++ b/solidity/test/unit/modules/dispute/RootVerificationModule.t.sol @@ -1,338 +1,304 @@ -// // SPDX-License-Identifier: AGPL-3.0-only -// pragma solidity ^0.8.19; - -// import 'forge-std/Test.sol'; - -// import {Helpers} from '../../../utils/Helpers.sol'; - -// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; -// import {IModule} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IModule.sol'; - -// import { -// RootVerificationModule, -// IRootVerificationModule -// } from '../../../../contracts/modules/dispute/RootVerificationModule.sol'; - -// import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountingExtension.sol'; -// import {ITreeVerifier} from '../../../../interfaces/ITreeVerifier.sol'; - -// /** -// * @dev Harness to set an entry in the requestData mapping, without triggering setup request hooks -// */ -// contract ForTest_RootVerificationModule is RootVerificationModule { -// constructor(IOracle _oracle) RootVerificationModule(_oracle) {} - -// function forTest_setRequestData(bytes32 _requestId, bytes memory _data) public { -// requestData[_requestId] = _data; -// } -// } - -// /** -// * @title Root Verification Module Unit tests -// */ -// contract BaseTest is Test, Helpers { -// // The target contract -// ForTest_RootVerificationModule public rootVerificationModule; -// // A mock oracle -// IOracle public oracle; -// // A mock accounting extension -// IAccountingExtension public accountingExtension; -// // Some unnoticeable dude -// address public dude = makeAddr('dude'); -// // 100% random sequence of bytes representing request, response, or dispute id -// bytes32 public mockId = bytes32('69'); -// // Create a new dummy dispute -// IOracle.Dispute public mockDispute; -// // A mock tree verifier -// ITreeVerifier public treeVerifier; -// // Mock addresses -// IERC20 public _token = IERC20(makeAddr('token')); -// address public _disputer = makeAddr('disputer'); -// address public _proposer = makeAddr('proposer'); - -// // Mock request data -// bytes32[32] internal _treeBranches = [ -// bytes32('branch1'), -// bytes32('branch2'), -// bytes32('branch3'), -// bytes32('branch4'), -// bytes32('branch5'), -// bytes32('branch6'), -// bytes32('branch7'), -// bytes32('branch8'), -// bytes32('branch9'), -// bytes32('branch10'), -// bytes32('branch11'), -// bytes32('branch12'), -// bytes32('branch13'), -// bytes32('branch14'), -// bytes32('branch15'), -// bytes32('branch16'), -// bytes32('branch17'), -// bytes32('branch18'), -// bytes32('branch19'), -// bytes32('branch20'), -// bytes32('branch21'), -// bytes32('branch22'), -// bytes32('branch23'), -// bytes32('branch24'), -// bytes32('branch25'), -// bytes32('branch26'), -// bytes32('branch27'), -// bytes32('branch28'), -// bytes32('branch29'), -// bytes32('branch30'), -// bytes32('branch31'), -// bytes32('branch32') -// ]; -// uint256 internal _treeCount = 1; -// bytes internal _treeData = abi.encode(_treeBranches, _treeCount); -// bytes32[] internal _leavesToInsert = [bytes32('leave1'), bytes32('leave2')]; - -// event ResponseDisputed(bytes32 indexed _requestId, bytes32 _responseId, address _disputer, address _proposer); - -// /** -// * @notice Deploy the target and mock oracle+accounting extension -// */ -// function setUp() public { -// oracle = IOracle(makeAddr('Oracle')); -// vm.etch(address(oracle), hex'069420'); - -// accountingExtension = IAccountingExtension(makeAddr('AccountingExtension')); -// vm.etch(address(accountingExtension), hex'069420'); -// treeVerifier = ITreeVerifier(makeAddr('TreeVerifier')); -// vm.etch(address(treeVerifier), hex'069420'); - -// rootVerificationModule = new ForTest_RootVerificationModule(oracle); - -// mockDispute = IOracle.Dispute({ -// disputer: dude, -// responseId: mockId, -// proposer: dude, -// requestId: mockId, - -// }); -// } -// } - -// contract RootVerificationModule_Unit_ModuleData is BaseTest { -// /** -// * @notice Test that the moduleName function returns the correct name -// */ -// function test_moduleNameReturnsName() public { -// assertEq(rootVerificationModule.moduleName(), 'RootVerificationModule'); -// } - -// /** -// * @notice Test that the decodeRequestData function returns the correct values -// */ -// function test_decodeRequestData_returnsCorrectData( -// bytes32 _requestId, -// address _accountingExtension, -// address _randomToken, -// uint256 _bondSize -// ) public { -// // Mock data -// bytes memory _requestData = abi.encode( -// IRootVerificationModule.RequestParameters({ -// treeData: _treeData, -// leavesToInsert: _leavesToInsert, -// treeVerifier: treeVerifier, -// accountingExtension: IAccountingExtension(_accountingExtension), -// bondToken: IERC20(_randomToken), -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// rootVerificationModule.forTest_setRequestData(_requestId, _requestData); - -// IRootVerificationModule.RequestParameters memory _params = rootVerificationModule.decodeRequestData(_requestId); - -// bytes32[32] memory _treeBranchesStored; -// uint256 _treeCountStored; -// (_treeBranchesStored, _treeCountStored) = abi.decode(_params.treeData, (bytes32[32], uint256)); - -// // Check: is the request data properly stored? -// for (uint256 _i = 0; _i < _treeBranches.length; _i++) { -// assertEq(_treeBranchesStored[_i], _treeBranches[_i], 'Mismatch: decoded tree branch'); -// } -// for (uint256 _i = 0; _i < _leavesToInsert.length; _i++) { -// assertEq(_params.leavesToInsert[_i], _leavesToInsert[_i], 'Mismatch: decoded leave to insert'); -// } -// assertEq(_treeCountStored, _treeCount, 'Mismatch: decoded tree count'); -// assertEq(address(_params.treeVerifier), address(treeVerifier), 'Mismatch: decoded tree verifier'); -// assertEq(address(_params.accountingExtension), _accountingExtension, 'Mismatch: decoded accounting extension'); -// assertEq(address(_params.bondToken), _randomToken, 'Mismatch: decoded token'); -// assertEq(_params.bondSize, _bondSize, 'Mismatch: decoded bond size'); -// } -// } - -// contract RootVerificationModule_Unit_DisputeResponse is BaseTest { -// /** -// * @notice Test if dispute incorrect response returns the correct status -// */ -// function test_disputeIncorrectResponse(bytes32 _requestId, bytes32 _responseId, uint256 _bondSize) public { -// vm.assume(_requestId != _responseId); - -// // Mock request data -// bytes memory _requestData = abi.encode( -// IRootVerificationModule.RequestParameters({ -// treeData: _treeData, -// leavesToInsert: _leavesToInsert, -// treeVerifier: treeVerifier, -// accountingExtension: accountingExtension, -// bondToken: _token, -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// rootVerificationModule.forTest_setRequestData(_requestId, _requestData); - -// // Create new Response memory struct with random values -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// proposer: _proposer, -// requestId: _requestId, -// disputeId: mockId, -// response: abi.encode(bytes32('randomRoot')) -// }); - -// // Mock and expect the call to the oracle, getting the response -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponse, (_responseId)), abi.encode(_mockResponse)); - -// // Mock and expect the call to the tree verifier, calculating the root -// _mockAndExpect( -// address(treeVerifier), -// abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), -// abi.encode(bytes32('randomRoot2')) -// ); - -// vm.prank(address(oracle)); -// IOracle.Dispute memory _dispute = -// rootVerificationModule.disputeResponse(_requestId, _responseId, _disputer, _proposer); - -// // Check: is the dispute data properly stored? -// assertEq(_dispute.disputer, _disputer, 'Mismatch: disputer'); -// assertEq(_dispute.proposer, _proposer, 'Mismatch: proposer'); -// assertEq(_dispute.responseId, _responseId, 'Mismatch: responseId'); -// assertEq(_dispute.requestId, _requestId, 'Mismatch: requestId'); -// assertEq(uint256(_dispute.status), uint256(IOracle.DisputeStatus.Won), 'Mismatch: status'); -// assertEq(_dispute.createdAt, block.timestamp, 'Mismatch: createdAt'); -// } - -// function test_emitsEvent(bytes32 _requestId, bytes32 _responseId, uint256 _bondSize) public { -// vm.assume(_requestId != _responseId); - -// // Mock request data -// bytes memory _requestData = abi.encode( -// IRootVerificationModule.RequestParameters({ -// treeData: _treeData, -// leavesToInsert: _leavesToInsert, -// treeVerifier: treeVerifier, -// accountingExtension: accountingExtension, -// bondToken: _token, -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// rootVerificationModule.forTest_setRequestData(_requestId, _requestData); - -// // Create new Response memory struct with random values -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// proposer: _proposer, -// requestId: _requestId, -// disputeId: mockId, -// response: abi.encode(bytes32('randomRoot')) -// }); - -// // Mock and expect the call to the oracle, getting the response -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponse, (_responseId)), abi.encode(_mockResponse)); - -// // Mock and expect the call to the tree verifier, calculating the root -// _mockAndExpect( -// address(treeVerifier), -// abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), -// abi.encode(bytes32('randomRoot2')) -// ); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(rootVerificationModule)); -// emit ResponseDisputed({_requestId: _dispute.requestId, _responseId: _dispute.responseId, _dispute: _dispute, blockNumber: block.number}); - -// vm.prank(address(oracle)); -// rootVerificationModule.disputeResponse(_requestId, _responseId, _disputer, _proposer); -// } - -// /** -// * @notice Test if dispute correct response returns the correct status -// */ -// function test_disputeCorrectResponse(bytes32 _requestId, bytes32 _responseId, uint256 _bondSize) public { -// vm.assume(_requestId != _responseId); - -// bytes memory _encodedCorrectRoot = abi.encode(bytes32('randomRoot')); - -// // Mock request data -// bytes memory _requestData = abi.encode( -// IRootVerificationModule.RequestParameters({ -// treeData: _treeData, -// leavesToInsert: _leavesToInsert, -// treeVerifier: treeVerifier, -// accountingExtension: accountingExtension, -// bondToken: _token, -// bondSize: _bondSize -// }) -// ); - -// // Store the mock request -// rootVerificationModule.forTest_setRequestData(_requestId, _requestData); - -// // Create new Response memory struct with random values -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// proposer: _proposer, -// requestId: _requestId, -// disputeId: mockId, -// response: _encodedCorrectRoot -// }); - -// // Mock and expect the call to the oracle, getting the response -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponse, (_responseId)), abi.encode(_mockResponse)); - -// // Mock and expect the call to the tree verifier, calculating the root -// _mockAndExpect( -// address(treeVerifier), -// abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), -// _encodedCorrectRoot -// ); - -// vm.prank(address(oracle)); -// IOracle.Dispute memory _dispute = -// rootVerificationModule.disputeResponse(_requestId, _responseId, _disputer, _proposer); - -// // Check: is the dispute data properly stored? -// assertEq(_dispute.disputer, _disputer, 'Mismatch: disputer'); -// assertEq(_dispute.proposer, _proposer, 'Mismatch: proposer'); -// assertEq(_dispute.responseId, _responseId, 'Mismatch: responseId'); -// assertEq(_dispute.requestId, _requestId, 'Mismatch: requestId'); -// assertEq(uint256(_dispute.status), uint256(IOracle.DisputeStatus.Lost), 'Mismatch: status'); -// assertEq(_dispute.createdAt, block.timestamp, 'Mismatch: createdAt'); -// } - -// /** -// * @notice Test if dispute response reverts when called by caller who's not the oracle -// */ -// function test_revertWrongCaller(address _randomCaller) public { -// vm.assume(_randomCaller != address(oracle)); - -// // Check: revert if not called by the Oracle? -// vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); - -// vm.prank(_randomCaller); -// rootVerificationModule.disputeResponse(mockId, mockId, dude, dude); -// } -// } +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; + +import 'forge-std/Test.sol'; + +import {Helpers} from '../../../utils/Helpers.sol'; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; +import {IModule} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IModule.sol'; + +import { + RootVerificationModule, + IRootVerificationModule +} from '../../../../contracts/modules/dispute/RootVerificationModule.sol'; + +import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountingExtension.sol'; +import {ITreeVerifier} from '../../../../interfaces/ITreeVerifier.sol'; + +/** + * @title Root Verification Module Unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + RootVerificationModule public rootVerificationModule; + // A mock oracle + IOracle public oracle; + // A mock accounting extension + IAccountingExtension public accountingExtension; + // A mock tree verifier + ITreeVerifier public treeVerifier; + // Mock addresses + IERC20 public _token = IERC20(makeAddr('token')); + + // Mock request data + bytes32[32] internal _treeBranches = [ + bytes32('branch1'), + bytes32('branch2'), + bytes32('branch3'), + bytes32('branch4'), + bytes32('branch5'), + bytes32('branch6'), + bytes32('branch7'), + bytes32('branch8'), + bytes32('branch9'), + bytes32('branch10'), + bytes32('branch11'), + bytes32('branch12'), + bytes32('branch13'), + bytes32('branch14'), + bytes32('branch15'), + bytes32('branch16'), + bytes32('branch17'), + bytes32('branch18'), + bytes32('branch19'), + bytes32('branch20'), + bytes32('branch21'), + bytes32('branch22'), + bytes32('branch23'), + bytes32('branch24'), + bytes32('branch25'), + bytes32('branch26'), + bytes32('branch27'), + bytes32('branch28'), + bytes32('branch29'), + bytes32('branch30'), + bytes32('branch31'), + bytes32('branch32') + ]; + uint256 internal _treeCount = 1; + bytes internal _treeData = abi.encode(_treeBranches, _treeCount); + bytes32[] internal _leavesToInsert = [bytes32('leave1'), bytes32('leave2')]; + + // Events + event ResponseDisputed( + bytes32 indexed _requestId, + bytes32 indexed _responseId, + bytes32 indexed _disputeId, + IOracle.Dispute _dispute, + uint256 _blockNumber + ); + + /** + * @notice Deploy the target and mock oracle+accounting extension + */ + function setUp() public { + oracle = IOracle(makeAddr('Oracle')); + vm.etch(address(oracle), hex'069420'); + + accountingExtension = IAccountingExtension(makeAddr('AccountingExtension')); + vm.etch(address(accountingExtension), hex'069420'); + + treeVerifier = ITreeVerifier(makeAddr('TreeVerifier')); + vm.etch(address(treeVerifier), hex'069420'); + + rootVerificationModule = new RootVerificationModule(oracle); + } +} + +contract RootVerificationModule_Unit_ModuleData is BaseTest { + /** + * @notice Test that the moduleName function returns the correct name + */ + function test_moduleNameReturnsName() public { + assertEq(rootVerificationModule.moduleName(), 'RootVerificationModule'); + } + + /** + * @notice Test that the decodeRequestData function returns the correct values + */ + function test_decodeRequestData_returnsCorrectData( + address _accountingExtension, + address _randomToken, + uint256 _bondSize + ) public { + // Mock data + bytes memory _requestData = abi.encode( + IRootVerificationModule.RequestParameters({ + treeData: _treeData, + leavesToInsert: _leavesToInsert, + treeVerifier: treeVerifier, + accountingExtension: IAccountingExtension(_accountingExtension), + bondToken: IERC20(_randomToken), + bondSize: _bondSize + }) + ); + + IRootVerificationModule.RequestParameters memory _params = rootVerificationModule.decodeRequestData(_requestData); + + bytes32[32] memory _treeBranchesStored; + uint256 _treeCountStored; + (_treeBranchesStored, _treeCountStored) = abi.decode(_params.treeData, (bytes32[32], uint256)); + + // Check: is the request data properly stored? + for (uint256 _i = 0; _i < _treeBranches.length; _i++) { + assertEq(_treeBranchesStored[_i], _treeBranches[_i], 'Mismatch: decoded tree branch'); + } + for (uint256 _i = 0; _i < _leavesToInsert.length; _i++) { + assertEq(_params.leavesToInsert[_i], _leavesToInsert[_i], 'Mismatch: decoded leave to insert'); + } + assertEq(_treeCountStored, _treeCount, 'Mismatch: decoded tree count'); + assertEq(address(_params.treeVerifier), address(treeVerifier), 'Mismatch: decoded tree verifier'); + assertEq(address(_params.accountingExtension), _accountingExtension, 'Mismatch: decoded accounting extension'); + assertEq(address(_params.bondToken), _randomToken, 'Mismatch: decoded token'); + assertEq(_params.bondSize, _bondSize, 'Mismatch: decoded bond size'); + } +} + +contract RootVerificationModule_Unit_DisputeResponse is BaseTest { + /** + * @notice Test if dispute incorrect response returns the correct status + */ + function test_disputeIncorrectResponse( + bytes memory _treeData, + bytes32[] calldata _leavesToInsert, + IAccountingExtension _accountingExtension, + uint256 _bondSize + ) public { + mockRequest.disputeModuleData = abi.encode( + IRootVerificationModule.RequestParameters({ + treeData: _treeData, + leavesToInsert: _leavesToInsert, + treeVerifier: treeVerifier, + accountingExtension: _accountingExtension, + bondToken: _token, + bondSize: _bondSize + }) + ); + + // Create new Response memory struct with random values + mockResponse.response = abi.encode(bytes32('randomRoot')); + + // Mock and expect the call to the tree verifier, calculating the root + _mockAndExpect( + address(treeVerifier), + abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), + abi.encode(bytes32('randomRoot2')) + ); + + // Mock and expect the call the oracle, updating the dispute's status + _mockAndExpect( + address(oracle), + abi.encodeWithSelector( + oracle.updateDisputeStatus.selector, mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Won + ), + abi.encode(true) + ); + + vm.prank(address(oracle)); + rootVerificationModule.disputeResponse(mockRequest, mockResponse, mockDispute); + } + + function test_emitsEvent( + bytes memory _treeData, + bytes32[] calldata _leavesToInsert, + IAccountingExtension _accountingExtension, + uint256 _bondSize + ) public { + mockRequest.disputeModuleData = abi.encode( + IRootVerificationModule.RequestParameters({ + treeData: _treeData, + leavesToInsert: _leavesToInsert, + treeVerifier: treeVerifier, + accountingExtension: _accountingExtension, + bondToken: _token, + bondSize: _bondSize + }) + ); + + // Create new Response memory struct with random values + mockResponse.response = abi.encode(bytes32('randomRoot')); + mockResponse.requestId = _getId(mockRequest); + mockDispute.requestId = _getId(mockRequest); + mockDispute.responseId = _getId(mockResponse); + + // Mock and expect the call to the tree verifier, calculating the root + _mockAndExpect( + address(treeVerifier), + abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), + abi.encode(bytes32('randomRoot2')) + ); + + // Mock and expect the call the oracle, updating the dispute's status + _mockAndExpect( + address(oracle), + abi.encodeWithSelector( + oracle.updateDisputeStatus.selector, mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Won + ), + abi.encode(true) + ); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(rootVerificationModule)); + emit ResponseDisputed({ + _requestId: mockDispute.requestId, + _responseId: mockDispute.responseId, + _disputeId: _getId(mockDispute), + _dispute: mockDispute, + _blockNumber: block.number + }); + + vm.prank(address(oracle)); + rootVerificationModule.disputeResponse(mockRequest, mockResponse, mockDispute); + } + + /** + * @notice Test if dispute correct response returns the correct status + */ + function test_disputeCorrectResponse( + bytes memory _treeData, + bytes32[] calldata _leavesToInsert, + IAccountingExtension _accountingExtension, + uint256 _bondSize + ) public { + mockRequest.disputeModuleData = abi.encode( + IRootVerificationModule.RequestParameters({ + treeData: _treeData, + leavesToInsert: _leavesToInsert, + treeVerifier: treeVerifier, + accountingExtension: _accountingExtension, + bondToken: _token, + bondSize: _bondSize + }) + ); + + bytes memory _encodedCorrectRoot = abi.encode(bytes32('randomRoot')); + + // Create new Response memory struct with random values + mockResponse.response = _encodedCorrectRoot; + mockResponse.requestId = _getId(mockRequest); + + // Mock and expect the call to the tree verifier, calculating the root + _mockAndExpect( + address(treeVerifier), + abi.encodeCall(ITreeVerifier.calculateRoot, (_treeData, _leavesToInsert)), + _encodedCorrectRoot + ); + + // Mock and expect the call the oracle, updating the dispute's status + _mockAndExpect( + address(oracle), + abi.encodeWithSelector( + oracle.updateDisputeStatus.selector, mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Lost + ), + abi.encode(true) + ); + + vm.prank(address(oracle)); + rootVerificationModule.disputeResponse(mockRequest, mockResponse, mockDispute); + } + + /** + * @notice Test if dispute response reverts when called by caller who's not the oracle + */ + function test_revertWrongCaller(address _randomCaller) public { + vm.assume(_randomCaller != address(oracle)); + + // Check: revert if not called by the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(_randomCaller); + rootVerificationModule.disputeResponse(mockRequest, mockResponse, mockDispute); + } +} From 592170985a78f386feb82ea4a386f58cfd418798 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:03:53 +0400 Subject: [PATCH 7/7] docs: remove an obsolete function --- .../content/modules/dispute/root_verification_module.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/src/content/modules/dispute/root_verification_module.md b/docs/src/content/modules/dispute/root_verification_module.md index 6e6e5fb6..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(bytes calldata _data)`: 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