From c82e4764a500871a4ae711d3abc20928e5d6db77 Mon Sep 17 00:00:00 2001 From: Gas <86567384+gas1cent@users.noreply.github.com> Date: Sat, 11 Nov 2023 15:45:04 +0400 Subject: [PATCH] perf: optimize `BondedResponseModule` (#3) --- .../response/bonded_response_module.md | 13 +- package.json | 2 +- .../modules/response/BondedResponseModule.sol | 209 ++--- .../extensions/IAccountingExtension.sol | 380 ++++----- .../response/IBondedResponseModule.sol | 193 ++--- .../test/integration/ResponseProposal.t.sol | 36 - .../response/BondedResponseModule.t.sol | 806 ++++++++---------- solidity/test/utils/Helpers.sol | 157 ++-- yarn.lock | 58 +- 9 files changed, 851 insertions(+), 1003 deletions(-) diff --git a/docs/src/content/modules/response/bonded_response_module.md b/docs/src/content/modules/response/bonded_response_module.md index b8c98800..4aaa6c67 100644 --- a/docs/src/content/modules/response/bonded_response_module.md +++ b/docs/src/content/modules/response/bonded_response_module.md @@ -10,10 +10,9 @@ The Bonded Response Module is a contract that allows users to propose a response ### Key Methods -- `decodeRequestData(bytes32 _requestId)`: Returns the decoded data for a request. -- `propose(bytes32 _requestId, address _proposer, bytes calldata _responseData)`: Proposes a response for a request, bonding the proposer's tokens. -- `deleteResponse(bytes32 _requestId, bytes32 _responseId, address _proposer)`: Allows a user to delete an undisputed response they proposed before the deadline, releasing the bond. -- `finalizeRequest(bytes32 _requestId, address _finalizer)`: Finalizes the request. +- `decodeRequestData`: Returns the decoded data for a request. +- `propose`: Proposes a response for a request, bonding the proposer's tokens. +- `finalizeRequest`: Finalizes the request. ### Request Parameters @@ -24,14 +23,12 @@ The Bonded Response Module is a contract that allows users to propose a response ## 3. Key Mechanisms & Concepts -- Deleting a response: If a proposer realizes the response they've submitted is incorrect, they can delete it. Note that disputed responses cannot be taken back. - - Early finalization: It is possible for pre-dispute modules to atomically calculate the correct response on-chain, decide on the result of a dispute and finalize the request before its deadline. +- Dispute window: Prevents proposers from submitting a response 1 block before the deadline and finalizing it in the next block, leaving disputers no time to dispute the response. + ## 4. Gotchas - In case of no valid responses, a request can be finalized after the deadline and the requester will get back their tokens. -- A proposer might submit a response 1 block before the deadline and finalize it in the next block, making it impossible to dispute. - Users cannot propose a response after the deadline for a request. - Users cannot propose a response if an undisputed response has already been proposed. -- Users cannot delete a response after the proposing deadline. diff --git a/package.json b/package.json index b7700bc1..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-d05a00d0", + "@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/solidity/contracts/modules/response/BondedResponseModule.sol b/solidity/contracts/modules/response/BondedResponseModule.sol index 3dffd2fb..c52bf5df 100644 --- a/solidity/contracts/modules/response/BondedResponseModule.sol +++ b/solidity/contracts/modules/response/BondedResponseModule.sol @@ -1,118 +1,91 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; - -// // solhint-disable-next-line no-unused-import -// import {Module, IModule} from '@defi-wonderland/prophet-core-contracts/solidity/contracts/Module.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; -// import {IBondedResponseModule} from '../../../interfaces/modules/response/IBondedResponseModule.sol'; - -// contract BondedResponseModule is Module, IBondedResponseModule { -// constructor(IOracle _oracle) Module(_oracle) {} - -// /// @inheritdoc IModule -// function moduleName() public pure returns (string memory _moduleName) { -// _moduleName = 'BondedResponseModule'; -// } - -// /// @inheritdoc IBondedResponseModule -// function decodeRequestData(bytes32 _requestId) public view returns (RequestParameters memory _params) { -// _params = abi.decode(requestData[_requestId], (RequestParameters)); -// } - -// /// @inheritdoc IBondedResponseModule -// function propose( -// bytes32 _requestId, -// address _proposer, -// bytes calldata _responseData, -// address _sender -// ) external onlyOracle returns (IOracle.Response memory _response) { -// RequestParameters memory _params = decodeRequestData(_requestId); - -// // Cannot propose after the deadline -// if (block.timestamp >= _params.deadline) revert BondedResponseModule_TooLateToPropose(); - -// // Cannot propose to a request with a response, unless the response is being disputed -// bytes32[] memory _responseIds = ORACLE.getResponseIds(_requestId); -// uint256 _responsesLength = _responseIds.length; - -// if (_responsesLength != 0) { -// bytes32 _disputeId = ORACLE.getResponse(_responseIds[_responsesLength - 1]).disputeId; - -// // Allowing one undisputed response at a time -// if (_disputeId == bytes32(0)) revert BondedResponseModule_AlreadyResponded(); -// IOracle.Dispute memory _dispute = ORACLE.getDispute(_disputeId); -// // TODO: leaving a note here to re-check this check if a new status is added -// // If the dispute was lost, we assume the proposed answer was correct. DisputeStatus.None should not be reachable due to the previous check. -// if (_dispute.status == IOracle.DisputeStatus.Lost) revert BondedResponseModule_AlreadyResponded(); -// } - -// _response = IOracle.Response({ -// requestId: _requestId, -// disputeId: bytes32(0), -// proposer: _proposer, -// response: _responseData, -// createdAt: block.timestamp -// }); - -// _params.accountingExtension.bond({ -// _bonder: _response.proposer, -// _requestId: _requestId, -// _token: _params.bondToken, -// _amount: _params.bondSize, -// _sender: _sender -// }); - -// emit ProposeResponse(_requestId, _proposer, _responseData); -// } - -// /// @inheritdoc IBondedResponseModule -// function deleteResponse(bytes32 _requestId, bytes32, address _proposer) external onlyOracle { -// RequestParameters memory _params = decodeRequestData(_requestId); - -// if (block.timestamp > _params.deadline) revert BondedResponseModule_TooLateToDelete(); - -// _params.accountingExtension.release({ -// _bonder: _proposer, -// _requestId: _requestId, -// _token: _params.bondToken, -// _amount: _params.bondSize -// }); -// } - -// /// @inheritdoc IBondedResponseModule -// function finalizeRequest( -// bytes32 _requestId, -// address _finalizer -// ) external override(IBondedResponseModule, Module) onlyOracle { -// RequestParameters memory _params = decodeRequestData(_requestId); - -// bool _isModule = ORACLE.allowedModule(_requestId, _finalizer); - -// if (!_isModule && block.timestamp < _params.deadline) { -// revert BondedResponseModule_TooEarlyToFinalize(); -// } - -// IOracle.Response memory _response = ORACLE.getFinalizedResponse(_requestId); -// if (_response.createdAt != 0) { -// if (!_isModule && block.timestamp < _response.createdAt + _params.disputeWindow) { -// revert BondedResponseModule_TooEarlyToFinalize(); -// } - -// _params.accountingExtension.release({ -// _bonder: _response.proposer, -// _requestId: _requestId, -// _token: _params.bondToken, -// _amount: _params.bondSize -// }); -// } -// emit RequestFinalized(_requestId, _finalizer); -// } - -// /// @inheritdoc Module -// function _afterSetupRequest(bytes32, bytes calldata _data) internal view override { -// RequestParameters memory _params = abi.decode(_data, (RequestParameters)); -// if (_params.deadline <= block.timestamp) { -// revert BondedResponseModule_InvalidRequest(); -// } -// } -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +// solhint-disable-next-line no-unused-import +import {Module, IModule} from '@defi-wonderland/prophet-core-contracts/solidity/contracts/Module.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; +import {IBondedResponseModule} from '../../../interfaces/modules/response/IBondedResponseModule.sol'; + +contract BondedResponseModule is Module, IBondedResponseModule { + constructor(IOracle _oracle) Module(_oracle) {} + + /// @inheritdoc IModule + function moduleName() public pure returns (string memory _moduleName) { + _moduleName = 'BondedResponseModule'; + } + + /// @inheritdoc IBondedResponseModule + function decodeRequestData(bytes calldata _data) public pure returns (RequestParameters memory _params) { + _params = abi.decode(_data, (RequestParameters)); + } + + /// @inheritdoc IBondedResponseModule + function propose( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + address _sender + ) external onlyOracle { + RequestParameters memory _params = decodeRequestData(_request.responseModuleData); + + // Cannot propose after the deadline + if (block.timestamp >= _params.deadline) revert BondedResponseModule_TooLateToPropose(); + + // Cannot propose to a request with a response, unless the response is being disputed + bytes32[] memory _responseIds = ORACLE.getResponseIds(_response.requestId); + uint256 _responsesLength = _responseIds.length; + + if (_responsesLength != 0) { + bytes32 _disputeId = ORACLE.disputeOf(_responseIds[_responsesLength - 1]); + + // Allowing one undisputed response at a time + if (_disputeId == bytes32(0)) revert BondedResponseModule_AlreadyResponded(); + IOracle.DisputeStatus _status = ORACLE.disputeStatus(_disputeId); + // TODO: leaving a note here to re-check this check if a new status is added + // If the dispute was lost, we assume the proposed answer was correct. DisputeStatus.None should not be reachable due to the previous check. + if (_status == IOracle.DisputeStatus.Lost) revert BondedResponseModule_AlreadyResponded(); + } + + _params.accountingExtension.bond({ + _bonder: _response.proposer, + _requestId: _response.requestId, + _token: _params.bondToken, + _amount: _params.bondSize, + _sender: _sender + }); + + emit ResponseProposed(_response.requestId, _response, block.number); + } + + /// @inheritdoc IBondedResponseModule + function finalizeRequest( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + address _finalizer + ) external override(IBondedResponseModule, Module) onlyOracle { + RequestParameters memory _params = decodeRequestData(_request.responseModuleData); + + // TODO: If deadline has passed, we can skip the caller validation + bool _isModule = ORACLE.allowedModule(_response.requestId, _finalizer); + + if (!_isModule && block.timestamp < _params.deadline) { + revert BondedResponseModule_TooEarlyToFinalize(); + } + + uint256 _responseCreatedAt = ORACLE.createdAt(_getId(_response)); + + if (_responseCreatedAt != 0) { + if (!_isModule && block.timestamp < _responseCreatedAt + _params.disputeWindow) { + revert BondedResponseModule_TooEarlyToFinalize(); + } + + _params.accountingExtension.release({ + _bonder: _response.proposer, + _requestId: _response.requestId, + _token: _params.bondToken, + _amount: _params.bondSize + }); + } + + emit RequestFinalized(_response.requestId, _response, _finalizer); + } +} diff --git a/solidity/interfaces/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/interfaces/modules/response/IBondedResponseModule.sol b/solidity/interfaces/modules/response/IBondedResponseModule.sol index c159b988..1b460221 100644 --- a/solidity/interfaces/modules/response/IBondedResponseModule.sol +++ b/solidity/interfaces/modules/response/IBondedResponseModule.sol @@ -1,117 +1,102 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; -// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; -// import {IResponseModule} from -// '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/response/IResponseModule.sol'; +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; +import {IResponseModule} from + '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/response/IResponseModule.sol'; -// import {IAccountingExtension} from '../../extensions/IAccountingExtension.sol'; +import {IAccountingExtension} from '../../extensions/IAccountingExtension.sol'; -// /* -// * @title BondedResponseModule -// * @notice Module allowing users to propose a response for a request -// * by bonding tokens. -// */ -// interface IBondedResponseModule is IResponseModule { -// /*/////////////////////////////////////////////////////////////// -// ERRORS -// //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Emitted when a response is proposed -// * @param _requestId The ID of the request that the response was proposed -// * @param _proposer The user that proposed the response -// * @param _responseData The data for the response -// */ -// event ProposeResponse(bytes32 indexed _requestId, address _proposer, bytes _responseData); -// /*/////////////////////////////////////////////////////////////// -// ERRORS -// //////////////////////////////////////////////////////////////*/ +/* + * @title BondedResponseModule + * @notice Module allowing users to propose a response for a request by bonding tokens + */ +interface IBondedResponseModule is IResponseModule { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + /** + * @notice Emitted when a response is proposed + * + * @param _requestId The ID of the request that the response was proposed + * @param _response The proposed response + * @param _blockNumber The number of the block in which the response was proposed + */ + event ResponseProposed(bytes32 indexed _requestId, IOracle.Response _response, uint256 indexed _blockNumber); -// /** -// * @notice Thrown when trying to finalize a request before the deadline -// */ -// error BondedResponseModule_TooEarlyToFinalize(); + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Thrown when trying to propose a response after deadline -// */ -// error BondedResponseModule_TooLateToPropose(); + /** + * @notice Thrown when trying to finalize a request before the deadline + */ + error BondedResponseModule_TooEarlyToFinalize(); -// /** -// * @notice Thrown when trying to propose a response while an undisputed response is already proposed -// */ -// error BondedResponseModule_AlreadyResponded(); + /** + * @notice Thrown when trying to propose a response after deadline + */ + error BondedResponseModule_TooLateToPropose(); -// /** -// * @notice Thrown when trying to delete a response after the proposing deadline -// */ -// error BondedResponseModule_TooLateToDelete(); + /** + * @notice Thrown when trying to propose a response while an undisputed response is already proposed + */ + error BondedResponseModule_AlreadyResponded(); -// /** -// * @notice Thrown when trying to create an invalid request -// */ -// error BondedResponseModule_InvalidRequest(); + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ -// /*/////////////////////////////////////////////////////////////// -// STRUCTS -// //////////////////////////////////////////////////////////////*/ + /** + * @notice Parameters of the request as stored in the module + * + * @param accountingExtension The accounting extension used to bond and release tokens + * @param bondToken The token used for bonds in the request + * @param bondSize The amount of `_bondToken` to bond to propose a response and dispute + * @param deadline The timestamp after which no responses can be proposed + * @param disputeWindow The time buffer required to finalize a request + */ + struct RequestParameters { + IAccountingExtension accountingExtension; + IERC20 bondToken; + uint256 bondSize; + uint256 deadline; + uint256 disputeWindow; + } -// /** -// * @notice Parameters of the request as stored in the module -// * @param accountingExtension The accounting extension used to bond and release tokens -// * @param bondToken The token used for bonds in the request -// * @param bondSize The amount of `_bondToken` to bond to propose a response and dispute -// * @param deadline The timestamp after which no responses can be proposed -// * @param disputeWindow The time buffer required to finalize a request -// */ -// struct RequestParameters { -// IAccountingExtension accountingExtension; -// IERC20 bondToken; -// uint256 bondSize; -// uint256 deadline; -// uint256 disputeWindow; -// } + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ -// /*/////////////////////////////////////////////////////////////// -// LOGIC -// //////////////////////////////////////////////////////////////*/ + /** + * @notice Returns the decoded data for a request + * + * @param _data The encoded data + * @return _params The struct containing the parameters for the request + */ + function decodeRequestData(bytes calldata _data) external pure returns (RequestParameters memory _params); -// /** -// * @notice Returns the decoded data for a request -// * @param _requestId The ID of the request -// * @return _params The struct containing the parameters for the request -// */ -// function decodeRequestData(bytes32 _requestId) external view returns (RequestParameters memory _params); + /** + * @notice Proposes a response for a request, bonding the proposer's tokens + * + * @dev The user must have previously deposited tokens into the accounting extension + * @param _request The request to propose a response to + * @param _response The response being proposed + * @param _sender The address that initiated the transaction + */ + function propose(IOracle.Request calldata _request, IOracle.Response calldata _response, address _sender) external; -// /** -// * @notice Proposes a response for a request, bonding the proposer's tokens -// * @dev The user must have previously deposited tokens into the accounting extension -// * @param _requestId The ID of the request to propose a response for -// * @param _proposer The user proposing the response -// * @param _responseData The data for the response -// * @param _sender The address calling propose on the Oracle -// * @return _response The struct of proposed response -// */ -// function propose( -// bytes32 _requestId, -// address _proposer, -// bytes calldata _responseData, -// address _sender -// ) external returns (IOracle.Response memory _response); - -// /** -// * @notice Allows a user to delete an undisputed response they proposed before the deadline, releasing the bond -// * @param _requestId The ID of the request to delete the response from -// * @param _responseId The ID of the response to delete -// * @param _proposer The user who proposed the response -// */ -// function deleteResponse(bytes32 _requestId, bytes32 _responseId, address _proposer) external; - -// /** -// * @notice Finalizes the request by releasing the bond of the proposer -// * @param _requestId The ID of the request to finalize -// * @param _finalizer The user who triggered the finalization -// */ -// function finalizeRequest(bytes32 _requestId, address _finalizer) external; -// } + /** + * @notice Finalizes the request by releasing the bond of the proposer + * + * @param _request The request that is being finalized + * @param _response The final response + * @param _finalizer The user who triggered the finalization + */ + function finalizeRequest( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + address _finalizer + ) external; +} diff --git a/solidity/test/integration/ResponseProposal.t.sol b/solidity/test/integration/ResponseProposal.t.sol index d7809076..a17486d7 100644 --- a/solidity/test/integration/ResponseProposal.t.sol +++ b/solidity/test/integration/ResponseProposal.t.sol @@ -149,40 +149,4 @@ // assertEq(_deletedResponse.createdAt, 0); // assertEq(_deletedResponse.disputeId, bytes32(0)); // } - -// function test_deleteResponse_afterDeadline(bytes memory _responseData, uint256 _timestamp) public { -// vm.assume(_timestamp > _expectedDeadline); - -// _forBondDepositERC20(_accountingExtension, proposer, usdc, _expectedBondSize, _expectedBondSize); - -// vm.startPrank(proposer); -// _accountingExtension.approveModule(address(_responseModule)); -// bytes32 _responseId = oracle.proposeResponse(_requestId, _responseData); -// vm.stopPrank(); - -// vm.warp(_timestamp); - -// vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooLateToDelete.selector); - -// vm.prank(proposer); -// oracle.deleteResponse(_responseId); -// } - -// function test_proposeResponse_finalizedRequest(bytes memory _responseData, uint256 _timestamp) public { -// vm.assume(_timestamp > _expectedDeadline + _baseDisputeWindow); - -// _forBondDepositERC20(_accountingExtension, proposer, usdc, _expectedBondSize, _expectedBondSize); - -// vm.startPrank(proposer); -// _accountingExtension.approveModule(address(_responseModule)); -// bytes32 _responseId = oracle.proposeResponse(_requestId, _responseData); -// vm.stopPrank(); - -// vm.warp(_timestamp); -// oracle.finalize(_requestId, _responseId); - -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); -// vm.prank(proposer); -// oracle.proposeResponse(_requestId, _responseData); -// } // } diff --git a/solidity/test/unit/modules/response/BondedResponseModule.t.sol b/solidity/test/unit/modules/response/BondedResponseModule.t.sol index 9d16f581..30c2133c 100644 --- a/solidity/test/unit/modules/response/BondedResponseModule.t.sol +++ b/solidity/test/unit/modules/response/BondedResponseModule.t.sol @@ -1,461 +1,345 @@ -// // SPDX-License-Identifier: AGPL-3.0-only -// pragma solidity ^0.8.19; - -// import 'forge-std/Test.sol'; - -// import {Helpers} from '../../../utils/Helpers.sol'; - -// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; - -// import { -// BondedResponseModule, -// IBondedResponseModule, -// IModule, -// IOracle -// } from '../../../../contracts/modules/response/BondedResponseModule.sol'; - -// import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountingExtension.sol'; - -// /** -// * @dev Harness to set an entry in the requestData mapping, without triggering setup request hooks -// */ -// contract ForTest_BondedResponseModule is BondedResponseModule { -// constructor(IOracle _oracle) BondedResponseModule(_oracle) {} - -// function forTest_setRequestData(bytes32 _requestId, bytes memory _data) public { -// requestData[_requestId] = _data; -// } -// } - -// /** -// * @title Bonded Response Module Unit tests -// */ -// contract BaseTest is Test, Helpers { -// // The target contract -// ForTest_BondedResponseModule public bondedResponseModule; -// // A mock oracle -// IOracle public oracle; -// // A mock accounting extension -// IAccountingExtension public accounting = IAccountingExtension(makeAddr('accounting')); -// // Base dispute window -// uint256 internal _baseDisputeWindow = 12 hours; - -// event ProposeResponse(bytes32 indexed _requestId, address _proposer, bytes _responseData); -// event RequestFinalized(bytes32 indexed _requestId, address _finalizer); - -// /** -// * @notice Deploy the target and mock oracle+accounting extension -// */ -// function setUp() public { -// oracle = IOracle(makeAddr('Oracle')); -// vm.etch(address(oracle), hex'069420'); - -// // vm.etch(address(token), hex'069420'); - -// // Avoid starting at 0 for time sensitive tests -// vm.warp(123_456); - -// bondedResponseModule = new ForTest_BondedResponseModule(oracle); -// } -// } - -// contract BondedResponseModule_Unit_ModuleData is BaseTest { -// /** -// * @notice Test that the moduleName function returns the correct name -// */ -// function test_moduleNameReturnsName() public { -// assertEq(bondedResponseModule.moduleName(), 'BondedResponseModule'); -// } - -// /** -// * @notice Test that the decodeRequestData function returns the correct values -// */ -// function test_decodeRequestData( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _disputeWindow, -// IERC20 _token -// ) public { -// // Create and set some mock request data -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Get the returned values -// IBondedResponseModule.RequestParameters memory _params = bondedResponseModule.decodeRequestData(_requestId); - -// // Check: correct values returned? -// assertEq(address(_params.accountingExtension), address(accounting), 'Mismatch: accounting extension address'); -// assertEq(address(_params.bondToken), address(_token), 'Mismatch: token address'); -// assertEq(_params.bondSize, _bondSize, 'Mismatch: bond size'); -// assertEq(_params.deadline, _deadline, 'Mismatch: deadline'); -// assertEq(_params.disputeWindow, _disputeWindow, 'Mismatch: dispute window'); -// } -// } - -// contract BondedResponseModule_Unit_Setup is BaseTest { -// function test_setupRequestRevertsIfInvalidRequest( -// IAccountingExtension _accounting, -// IERC20 _token, -// uint256 _deadline, -// uint256 _bondSize -// ) public { -// _deadline = bound(_deadline, 0, block.timestamp); - -// IBondedResponseModule.RequestParameters memory _requestParams = IBondedResponseModule.RequestParameters({ -// accountingExtension: _accounting, -// bondToken: _token, -// bondSize: _bondSize, -// deadline: _deadline, -// disputeWindow: _baseDisputeWindow -// }); - -// // Check: does it revert if the provided deadline is invalid? -// vm.expectRevert(IBondedResponseModule.BondedResponseModule_InvalidRequest.selector); -// vm.prank(address(oracle)); - -// bondedResponseModule.setupRequest(bytes32(0), abi.encode(_requestParams)); -// } -// } - -// contract BondedResponseModule_Unit_Propose is BaseTest { -// /** -// * @notice Test that the propose function is only callable by the oracle -// */ -// function test_revertIfNotOracle( -// bytes32 _requestId, -// address _sender, -// address _proposer, -// bytes calldata _responseData -// ) public { -// vm.assume(_sender != address(oracle)); - -// // Check: does it revert if not called by the Oracle? -// vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); - -// vm.prank(address(_sender)); -// bondedResponseModule.propose(_requestId, _proposer, _responseData, _sender); -// } - -// /** -// * @notice Test that the propose function works correctly and triggers _afterPropose (which bonds) -// */ -// function test_propose( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _disputeWindow, -// bytes calldata _responseData, -// address _sender, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); -// _disputeWindow = bound(_disputeWindow, 61, 365 days); -// _bondSize = bound(_bondSize, 0, type(uint248).max); - -// // Create and set some mock request data -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.getResponseIds to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponseIds, _requestId), abi.encode(new bytes32[](0))); - -// // Mock and expect IAccountingExtension.bond to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeWithSignature( -// 'bond(address,bytes32,address,uint256,address)', _proposer, _requestId, _token, _bondSize, _sender -// ), -// abi.encode() -// ); - -// vm.prank(address(oracle)); -// IOracle.Response memory _responseReturned = -// bondedResponseModule.propose(_requestId, _proposer, _responseData, _sender); - -// IOracle.Response memory _responseExpected = IOracle.Response({ -// createdAt: block.timestamp, -// requestId: _requestId, -// disputeId: bytes32(''), -// proposer: _proposer, -// response: _responseData -// }); - -// // Check: correct response struct returned? -// assertEq(_responseReturned.requestId, _responseExpected.requestId, 'Mismatch: request ID'); -// assertEq(_responseReturned.disputeId, _responseExpected.disputeId, 'Mismatch: dispute ID'); -// assertEq(_responseReturned.proposer, _responseExpected.proposer, 'Mismatch: proposer address'); -// assertEq(_responseReturned.response, _responseExpected.response, 'Mismatch: response object'); -// } - -// function test_emitsEvent( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _disputeWindow, -// bytes calldata _responseData, -// address _sender, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); -// _disputeWindow = bound(_disputeWindow, 61, 365 days); - -// // Create and set some mock request data -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.getResponseIds to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponseIds, _requestId), abi.encode(new bytes32[](0))); - -// // Mock and expect IOracle.getResponseIds to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeWithSignature( -// 'bond(address,bytes32,address,uint256,address)', _proposer, _requestId, _token, _bondSize, _sender -// ), -// abi.encode() -// ); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(bondedResponseModule)); -// emit ProposeResponse(_requestId, _proposer, _responseData); - -// vm.prank(address(oracle)); -// bondedResponseModule.propose(_requestId, _proposer, _responseData, _sender); -// } -// } - -// contract BondedResponseModule_Unit_FinalizeRequest is BaseTest { -// /** -// * @notice Test that the propose function is only callable by the oracle -// */ -// function test_calls( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _disputeWindow, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); -// _disputeWindow = bound(_disputeWindow, 61, 365 days); - -// // Check revert if deadline has not passed -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.allowedModule to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(false) -// ); - -// // Check: does it revert if it's too early to finalize? -// vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooEarlyToFinalize.selector); - -// vm.prank(address(oracle)); -// bondedResponseModule.finalizeRequest(_requestId, address(this)); - -// // Check correct calls are made if deadline has passed -// _deadline = block.timestamp; - -// _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// requestId: _requestId, -// disputeId: bytes32(''), -// proposer: _proposer, -// response: bytes('bleh') -// }); - -// // Mock and expect IOracle.getFinalizedResponse to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, _requestId), abi.encode(_mockResponse)); - -// // Mock and expect IAccountingExtension.release to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.release, (_proposer, _requestId, _token, _bondSize)), -// abi.encode(true) -// ); - -// vm.warp(block.timestamp + _disputeWindow); - -// vm.prank(address(oracle)); -// bondedResponseModule.finalizeRequest(_requestId, address(this)); -// } - -// function test_emitsEvent( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _disputeWindow, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); -// _disputeWindow = bound(_disputeWindow, 61, 365 days); - -// // Check revert if deadline has not passed -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.allowedModule to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(false) -// ); - -// // Check correct calls are made if deadline has passed -// _deadline = block.timestamp; - -// _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// requestId: _requestId, -// disputeId: bytes32(''), -// proposer: _proposer, -// response: bytes('bleh') -// }); - -// // Mock and expect IOracle.getFinalizedResponse to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, _requestId), abi.encode(_mockResponse)); - -// // Mock and expect IAccountingExtension.release to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.release, (_proposer, _requestId, _token, _bondSize)), -// abi.encode(true) -// ); - -// // Check: is event emitted? -// vm.expectEmit(true, true, true, true, address(bondedResponseModule)); -// emit RequestFinalized(_requestId, address(this)); - -// vm.warp(block.timestamp + _disputeWindow); - -// vm.prank(address(oracle)); -// bondedResponseModule.finalizeRequest(_requestId, address(this)); -// } - -// /** -// * @notice Test that the finalize function can be called by an allowed module before the time window. -// */ -// function test_earlyByModule( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); - -// address _allowedModule = makeAddr('allowed module'); -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _baseDisputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.allowedModule to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, _allowedModule)), abi.encode(true) -// ); - -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: block.timestamp, -// requestId: _requestId, -// disputeId: bytes32(''), -// proposer: _proposer, -// response: bytes('bleh') -// }); - -// // Mock and expect IOracle.getFinalizedResponse to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, _requestId), abi.encode(_mockResponse)); - -// // Mock and expect IAccountingExtension.release to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.release, (_proposer, _requestId, _token, _bondSize)), -// abi.encode(true) -// ); - -// vm.prank(address(oracle)); -// bondedResponseModule.finalizeRequest(_requestId, _allowedModule); -// } - -// /** -// * @notice Test that the finalizing a request during a response dispute window will revert. -// */ -// function test_revertDuringDisputeWindow( -// bytes32 _requestId, -// uint256 _bondSize, -// uint256 _deadline, -// IERC20 _token, -// address _proposer -// ) public { -// _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); - -// address _finalizer = makeAddr('finalizer'); -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _baseDisputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// // Mock and expect IOracle.allowedModule to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, _finalizer)), abi.encode(false)); - -// IOracle.Response memory _mockResponse = IOracle.Response({ -// createdAt: _deadline - 1, -// requestId: _requestId, -// disputeId: bytes32(''), -// proposer: _proposer, -// response: bytes('bleh') -// }); - -// // Mock and expect IOracle.getFinalizedResponse to be called -// _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getFinalizedResponse, _requestId), abi.encode(_mockResponse)); - -// vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooEarlyToFinalize.selector); - -// vm.warp(_deadline + 1); -// vm.prank(address(oracle)); -// bondedResponseModule.finalizeRequest(_requestId, _finalizer); -// } -// } - -// contract BondedResponseModule_Unit_DeleteResponse is BaseTest { -// /** -// * @notice Test that the delete response function triggers bond release. -// */ -// function test_deleteResponse( -// bytes32 _requestId, -// bytes32 _responseId, -// uint256 _bondSize, -// uint256 _deadline, -// uint256 _timestamp, -// IERC20 _token, -// address _proposer -// ) public { -// _timestamp = bound(_timestamp, 1, type(uint248).max); - -// // Create and set some mock request data -// bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _baseDisputeWindow); -// bondedResponseModule.forTest_setRequestData(_requestId, _data); - -// vm.warp(_timestamp); - -// if (_deadline >= _timestamp) { -// // Mock and expect IAccountingExtension.release to be called -// _mockAndExpect( -// address(accounting), -// abi.encodeCall(IAccountingExtension.release, (_proposer, _requestId, _token, _bondSize)), -// abi.encode() -// ); -// } else { -// // Check: does it revert if the deadline has passed? -// vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooLateToDelete.selector); -// } - -// vm.prank(address(oracle)); -// bondedResponseModule.deleteResponse(_requestId, _responseId, _proposer); -// } -// } +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; + +import 'forge-std/Test.sol'; + +import {Helpers} from '../../../utils/Helpers.sol'; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import { + BondedResponseModule, + IBondedResponseModule, + IModule, + IOracle +} from '../../../../contracts/modules/response/BondedResponseModule.sol'; + +import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountingExtension.sol'; + +/** + * @title Bonded Response Module Unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + BondedResponseModule public bondedResponseModule; + // A mock oracle + IOracle public oracle; + // A mock accounting extension + IAccountingExtension public accounting = IAccountingExtension(makeAddr('Accounting')); + // Base dispute window + uint256 internal _baseDisputeWindow = 12 hours; + + // Events + event ResponseProposed(bytes32 indexed _requestId, IOracle.Response _response, uint256 indexed _blockNumber); + + /** + * @notice Deploy the target and mock oracle+accounting extension + */ + function setUp() public { + oracle = IOracle(makeAddr('Oracle')); + vm.etch(address(oracle), hex'069420'); + + // Avoid starting at 0 for time sensitive tests + vm.warp(123_456); + + bondedResponseModule = new BondedResponseModule(oracle); + } +} + +contract BondedResponseModule_Unit_ModuleData is BaseTest { + /** + * @notice Test that the moduleName function returns the correct name + */ + function test_moduleNameReturnsName() public { + assertEq(bondedResponseModule.moduleName(), 'BondedResponseModule'); + } + + /** + * @notice Test that the decodeRequestData function returns the correct values + */ + function test_decodeRequestData(IERC20 _token, uint256 _bondSize, uint256 _deadline, uint256 _disputeWindow) public { + // Create and set some mock request data + bytes memory _data = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + + // Get the returned values + IBondedResponseModule.RequestParameters memory _params = bondedResponseModule.decodeRequestData(_data); + + // Check: correct values returned? + assertEq(address(_params.accountingExtension), address(accounting), 'Mismatch: accounting extension address'); + assertEq(address(_params.bondToken), address(_token), 'Mismatch: token address'); + assertEq(_params.bondSize, _bondSize, 'Mismatch: bond size'); + assertEq(_params.deadline, _deadline, 'Mismatch: deadline'); + assertEq(_params.disputeWindow, _disputeWindow, 'Mismatch: dispute window'); + } +} + +contract BondedResponseModule_Unit_Propose is BaseTest { + /** + * @notice Test that the propose function is only callable by the oracle + */ + function test_revertIfNotOracle(address _sender) public { + vm.assume(_sender != address(oracle)); + + // Check: does it revert if not called by the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(address(_sender)); + bondedResponseModule.propose(mockRequest, mockResponse, _sender); + } + + /** + * @notice Test that the propose function works correctly and bonds the proposer's funds + */ + function test_propose( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + uint256 _disputeWindow, + address _sender, + address _proposer + ) public assumeFuzzable(_sender) assumeFuzzable(_proposer) { + _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); + _disputeWindow = bound(_disputeWindow, 61, 365 days); + _bondSize = bound(_bondSize, 0, type(uint248).max); + + // Set the response module parameters + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.getResponseIds to be called + _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponseIds, _requestId), abi.encode(new bytes32[](0))); + + // Mock and expect IAccountingExtension.bond to be called + _mockAndExpect( + address(accounting), + abi.encodeWithSignature( + 'bond(address,bytes32,address,uint256,address)', _proposer, _requestId, _token, _bondSize, _sender + ), + abi.encode() + ); + + vm.prank(address(oracle)); + bondedResponseModule.propose(mockRequest, mockResponse, _sender); + } + + function test_emitsEvent( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + uint256 _disputeWindow, + address _sender, + address _proposer + ) public { + _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); + _disputeWindow = bound(_disputeWindow, 61, 365 days); + + // Create and set some mock request data + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.getResponseIds to be called + _mockAndExpect(address(oracle), abi.encodeCall(IOracle.getResponseIds, _requestId), abi.encode(new bytes32[](0))); + + // Mock and expect IOracle.getResponseIds to be called + _mockAndExpect( + address(accounting), + abi.encodeWithSignature( + 'bond(address,bytes32,address,uint256,address)', _proposer, _requestId, _token, _bondSize, _sender + ), + abi.encode() + ); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(bondedResponseModule)); + emit ResponseProposed({_requestId: _requestId, _response: mockResponse, _blockNumber: block.number}); + + vm.prank(address(oracle)); + bondedResponseModule.propose(mockRequest, mockResponse, _sender); + } +} + +contract BondedResponseModule_Unit_FinalizeRequest is BaseTest { + /** + * @notice Test that the propose function is only callable by the oracle + */ + function test_revertIfNotOracle(address _sender) public { + vm.assume(_sender != address(oracle)); + + // Check: does it revert if not called by the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(address(_sender)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, _sender); + } + + function test_revertsBeforeDeadline( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + uint256 _disputeWindow, + address _proposer + ) public { + _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); + _disputeWindow = bound(_disputeWindow, 61, 365 days); + + // Check revert if deadline has not passed + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + mockResponse.requestId = _getId(mockRequest); + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.allowedModule to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.allowedModule, (_getId(mockRequest), address(this))), abi.encode(false) + ); + + // Check: does it revert if it's too early to finalize? + vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooEarlyToFinalize.selector); + + vm.prank(address(oracle)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, address(this)); + } + + function test_releasesBond( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + uint256 _disputeWindow, + address _proposer + ) public { + _disputeWindow = bound(_disputeWindow, 61, 365 days); + + // Check correct calls are made if deadline has passed + _deadline = block.timestamp; + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + mockResponse.requestId = _getId(mockRequest); + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.allowedModule to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.allowedModule, (_getId(mockRequest), address(this))), abi.encode(true) + ); + + // Mock and expect IOracle.createdAt to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.createdAt, (_getId(mockResponse))), abi.encode(block.timestamp) + ); + + // Mock and expect IAccountingExtension.release to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.release, (_proposer, _getId(mockRequest), _token, _bondSize)), + abi.encode(true) + ); + + vm.warp(block.timestamp + _disputeWindow); + + vm.prank(address(oracle)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, address(this)); + } + + function test_emitsEvent(IERC20 _token, uint256 _bondSize, uint256 _disputeWindow, address _proposer) public { + _disputeWindow = bound(_disputeWindow, 61, 365 days); + + // Check correct calls are made if deadline has passed + uint256 _deadline = block.timestamp; + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.allowedModule to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(false) + ); + + // Mock and expect IOracle.createdAt to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.createdAt, (_getId(mockResponse))), abi.encode(block.timestamp) + ); + + // Mock and expect IAccountingExtension.release to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.release, (_proposer, _getId(mockRequest), _token, _bondSize)), + abi.encode(true) + ); + + // Check: is event emitted? + vm.expectEmit(true, true, true, true, address(bondedResponseModule)); + emit RequestFinalized({_requestId: _getId(mockRequest), _response: mockResponse, _finalizer: address(this)}); + + vm.warp(block.timestamp + _disputeWindow); + + vm.prank(address(oracle)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, address(this)); + } + + /** + * @notice Test that the finalize function can be called by an allowed module before the time window. + */ + function test_earlyByModule( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + address _proposer, + address _allowedModule + ) public assumeFuzzable(_allowedModule) { + _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _baseDisputeWindow); + + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.allowedModule to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, _allowedModule)), abi.encode(true) + ); + + // Mock and expect IOracle.createdAt to be called + _mockAndExpect( + address(oracle), + abi.encodeCall(IOracle.createdAt, (_getId(mockResponse))), + abi.encode(block.timestamp - _baseDisputeWindow) + ); + + // Mock and expect IAccountingExtension.release to be called + _mockAndExpect( + address(accounting), + abi.encodeCall(IAccountingExtension.release, (_proposer, _requestId, _token, _bondSize)), + abi.encode(true) + ); + + vm.prank(address(oracle)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, _allowedModule); + } + + /** + * @notice Test that the finalizing a request during a response dispute window will revert. + */ + function test_revertDuringDisputeWindow( + IERC20 _token, + uint256 _bondSize, + uint256 _deadline, + address _finalizer + ) public { + _deadline = bound(_deadline, block.timestamp + 1, type(uint248).max); + + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _baseDisputeWindow); + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + + // Mock and expect IOracle.allowedModule to be called + _mockAndExpect(address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, _finalizer)), abi.encode(false)); + + vm.expectRevert(IBondedResponseModule.BondedResponseModule_TooEarlyToFinalize.selector); + + vm.prank(address(oracle)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, _finalizer); + } +} diff --git a/solidity/test/utils/Helpers.sol b/solidity/test/utils/Helpers.sol index 46874489..213a0064 100644 --- a/solidity/test/utils/Helpers.sol +++ b/solidity/test/utils/Helpers.sol @@ -1,56 +1,101 @@ -// // 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'; +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); + + 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); + } + + // TODO: What does _balanceIncrease do? + 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(); + } + + /** + * @notice Computes the ID of a given request as it's done in the Oracle + * + * @param _request The request to compute the ID for + * @return _id The ID of the request + */ + function _getId(IOracle.Request memory _request) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_request)); + } + + /** + * @notice Computes the ID of a given response as it's done in the Oracle + * + * @param _response The response to compute the ID for + * @return _id The ID of the response + */ + function _getId(IOracle.Response memory _response) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_response)); + } + + /** + * @notice Computes the ID of a given dispute as it's done in the Oracle + * + * @param _dispute The dispute to compute the ID for + * @return _id The ID of the dispute + */ + function _getId(IOracle.Dispute memory _dispute) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_dispute)); + } +} diff --git a/yarn.lock b/yarn.lock index c61b7709..18287d95 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-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" @@ -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" @@ -1457,9 +1457,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" @@ -1576,7 +1576,7 @@ for-each@^0.3.3: "forge-std@https://github.com/foundry-rs/forge-std": version "1.7.1" - resolved "https://github.com/foundry-rs/forge-std#267acd30a625086b3f16e1a28cfe0c5097fa46b8" + resolved "https://github.com/foundry-rs/forge-std#37a37ab73364d6644bfe11edf88a07880f99bd56" "forge-std@https://github.com/foundry-rs/forge-std.git#f73c73d2018eb6a111f35e4dae7b4f27401e9421": version "1.7.1" @@ -2858,9 +2858,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 +3787,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 +3916,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"