Skip to content

Commit

Permalink
feat: ensure relative timestamps (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
xorsal authored Oct 11, 2024
1 parent 2d91875 commit b88fda6
Show file tree
Hide file tree
Showing 17 changed files with 239 additions and 106 deletions.
2 changes: 1 addition & 1 deletion docs/src/content/modules/dispute/bond_escalation_module.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The Bond Escalation Module is a contract that allows users to have the first dis
- `bondToken`: The address of the token associated with the given request.
- `bondSize`: The amount to bond to dispute or propose an answer for the given request.
- `maxNumberOfEscalations`: The maximum allowed escalations or pledges for each side during the bond escalation process.
- `bondEscalationDeadline`: The timestamp at which bond escalation process finishes when pledges are not tied.
- `bondEscalationDeadline`: The number of seconds after dispute creation required to finish the bond escalation process when pledges are not tied.
- `tyingBuffer`: The number of seconds to extend the bond escalation process to allow the losing party to tie if at the end of the initial deadline the pledges weren't tied.
- `disputeWindow`: The number of seconds disputers have to challenge the proposed response since its creation.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The Bonded Response Module is a contract that allows users to propose a response
- `accountingExtension`: The address holding the bonded tokens. It must implement the [IAccountingExtension.sol](/solidity/interfaces/extensions/IAccountingExtension.sol/interface.IAccountingExtension.md) interface.
- `bondToken`: The ERC20 token used for bonding.
- `bondSize`: The amount of tokens the disputer must bond to be able to dispute a response.
- `deadline`: The timestamp at which the module stops accepting new responses for a request and it becomes finalizable.
- `deadline`: The number of seconds after request creation at which the module stops accepting new responses for a request and it becomes finalizable.

## 3. Key Mechanisms & Concepts

Expand Down
20 changes: 15 additions & 5 deletions solidity/contracts/modules/dispute/BondEscalationModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ contract BondEscalationModule is Module, IBondEscalationModule {
// Only the first dispute of a request should go through the bond escalation
// Consecutive disputes should be handled by the resolution module
if (_escalation.status == BondEscalationStatus.None) {
if (block.timestamp > _params.bondEscalationDeadline) revert BondEscalationModule_BondEscalationOver();
if (block.timestamp > ORACLE.disputeCreatedAt(_disputeId) + _params.bondEscalationDeadline) {
revert BondEscalationModule_BondEscalationOver();
}
_escalation.status = BondEscalationStatus.Active;
_escalation.disputeId = _disputeId;
emit BondEscalationStatusUpdated(_dispute.requestId, _disputeId, BondEscalationStatus.Active);
Expand All @@ -83,7 +85,9 @@ contract BondEscalationModule is Module, IBondEscalationModule {
if (_disputeStatus == IOracle.DisputeStatus.Escalated) {
// The dispute has been escalated to the Resolution module
// Make sure the bond escalation deadline has passed and update the status
if (block.timestamp <= _params.bondEscalationDeadline) revert BondEscalationModule_BondEscalationNotOver();
if (block.timestamp <= ORACLE.disputeCreatedAt(_disputeId) + _params.bondEscalationDeadline) {
revert BondEscalationModule_BondEscalationNotOver();
}

if (
_escalation.status != BondEscalationStatus.Active
Expand Down Expand Up @@ -252,7 +256,8 @@ contract BondEscalationModule is Module, IBondEscalationModule {
RequestParameters memory _params = decodeRequestData(_request.disputeModuleData);
BondEscalation storage _escalation = _escalations[_dispute.requestId];

if (block.timestamp <= _params.bondEscalationDeadline + _params.tyingBuffer) {
uint256 _disputeCreatedAt = ORACLE.disputeCreatedAt(_disputeId);
if (block.timestamp <= _disputeCreatedAt + _params.bondEscalationDeadline + _params.tyingBuffer) {
revert BondEscalationModule_BondEscalationNotOver();
}

Expand Down Expand Up @@ -290,6 +295,7 @@ contract BondEscalationModule is Module, IBondEscalationModule {
bool _forDispute
) internal view returns (RequestParameters memory _params) {
bytes32 _disputeId = _validateDispute(_request, _dispute);

BondEscalation memory _escalation = _escalations[_dispute.requestId];

if (_disputeId != _escalation.disputeId) {
Expand All @@ -298,7 +304,8 @@ contract BondEscalationModule is Module, IBondEscalationModule {

_params = decodeRequestData(_request.disputeModuleData);

if (block.timestamp > _params.bondEscalationDeadline + _params.tyingBuffer) {
uint256 _disputeCreatedAt = ORACLE.disputeCreatedAt(_disputeId);
if (block.timestamp > _disputeCreatedAt + _params.bondEscalationDeadline + _params.tyingBuffer) {
revert BondEscalationModule_BondEscalationOver();
}

Expand All @@ -317,7 +324,10 @@ contract BondEscalationModule is Module, IBondEscalationModule {
if (_numPledgersAgainstDispute > _numPledgersForDispute) revert BondEscalationModule_CanOnlySurpassByOnePledge();
}

if (block.timestamp > _params.bondEscalationDeadline && _numPledgersForDispute == _numPledgersAgainstDispute) {
if (
block.timestamp > _disputeCreatedAt + _params.bondEscalationDeadline
&& _numPledgersForDispute == _numPledgersAgainstDispute
) {
revert BondEscalationModule_CannotBreakTieDuringTyingBuffer();
}
}
Expand Down
6 changes: 3 additions & 3 deletions solidity/contracts/modules/response/BondedResponseModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ contract BondedResponseModule is Module, IBondedResponseModule {
RequestParameters memory _params = decodeRequestData(_request.responseModuleData);

// Cannot propose after the deadline
if (block.timestamp >= _params.deadline) revert BondedResponseModule_TooLateToPropose();
uint256 _requestCreatedAt = ORACLE.requestCreatedAt(_response.requestId);
if (block.timestamp >= _requestCreatedAt + _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);
Expand Down Expand Up @@ -75,12 +76,11 @@ contract BondedResponseModule is Module, IBondedResponseModule {

bool _isModule = ORACLE.allowedModule(_response.requestId, _finalizer);

if (!_isModule && block.timestamp < _params.deadline) {
if (!_isModule && block.timestamp < ORACLE.requestCreatedAt(_response.requestId) + _params.deadline) {
revert BondedResponseModule_TooEarlyToFinalize();
}

uint256 _responseCreatedAt = ORACLE.responseCreatedAt(_getId(_response));

if (_responseCreatedAt != 0) {
if (!_isModule && block.timestamp < _responseCreatedAt + _params.disputeWindow) {
revert BondedResponseModule_TooEarlyToFinalize();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ interface IBondEscalationModule is IDisputeModule {
* @param bondToken Address of the token associated with the given request
* @param bondSize Amount to bond to dispute or propose an answer for the given request
* @param maxNumberOfEscalations Maximum allowed escalations or pledges for each side during the bond escalation process
* @param bondEscalationDeadline Timestamp at which bond escalation process finishes when pledges are not tied
* @param bondEscalationDeadline Number of seconds after dispute creation required to
* finish the bond escalation process when pledges are not tied.
* @param tyingBuffer Number of seconds to extend the bond escalation process to allow the losing
* party to tie if at the end of the initial deadline the pledges weren't tied.
* @param disputeWindow Number of seconds disputers have to challenge the proposed response since its creation.
Expand Down
22 changes: 11 additions & 11 deletions solidity/test/integration/BondEscalation.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ contract Integration_BondEscalation is IntegrationBase {
function setUp() public override {
super.setUp();

_expectedDeadline = block.timestamp + 10 days;
_bondEscalationDeadline = block.timestamp + 5 days;
_expectedDeadline = 10 days;
_bondEscalationDeadline = 5 days;

setUpRequest();
setUpEscalation();
Expand Down Expand Up @@ -128,7 +128,7 @@ contract Integration_BondEscalation is IntegrationBase {
// Step 4: Disputer runs out of capital
// Step 5: External parties see that Disputer's dispute was wrong so they don't join to escalate
// Step 6: Proposer response's is deemed correct and final once the bond escalation window is over
vm.warp(_expectedDeadline + _tyingBuffer + 1);
vm.warp(block.timestamp + _expectedDeadline + _tyingBuffer + 1);
_bondEscalationModule.settleBondEscalation(mockRequest, mockResponse, mockDispute);

IOracle.DisputeStatus _disputeStatus = oracle.disputeStatus(_disputeId);
Expand All @@ -152,7 +152,7 @@ contract Integration_BondEscalation is IntegrationBase {
assertEq(_bondEscalationAccounting.balanceOf(disputer, usdc), 0, 'Mismatch: Disputer balance');

// Step 8: Finalize request and check balances again
vm.warp(_expectedDeadline + 1 days);
vm.warp(block.timestamp + _expectedDeadline + 1 days);
oracle.finalize(mockRequest, mockResponse);

// Test: The requester has no balance because he has paid the proposer
Expand Down Expand Up @@ -189,7 +189,7 @@ contract Integration_BondEscalation is IntegrationBase {
// External parties see that Proposer's proposal was wrong so they don't join to escalate

// Step 5: Proposer response's is deemed incorrect. The bond escalation process along with the tying buffer is terminated
vm.warp(_bondEscalationDeadline + _tyingBuffer + 1);
vm.warp(block.timestamp + _bondEscalationDeadline + _tyingBuffer + 1);
_bondEscalationModule.settleBondEscalation(mockRequest, mockResponse, mockDispute);

IOracle.DisputeStatus _disputeStatus = oracle.disputeStatus(_disputeId);
Expand Down Expand Up @@ -248,7 +248,7 @@ contract Integration_BondEscalation is IntegrationBase {
_responseId = oracle.proposeResponse(mockRequest, _thirdResponse);

// Step 11: It goes undisputed for three days, therefore it's deemed correct and final
vm.warp(_expectedDeadline + 1);
vm.warp(block.timestamp + _expectedDeadline + 1);
oracle.finalize(mockRequest, _thirdResponse);

// Test: The requester has paid out the reward
Expand Down Expand Up @@ -283,7 +283,7 @@ contract Integration_BondEscalation is IntegrationBase {

// Step 12: Two days after the deadline, the resolution module says that Another proposer's answer was correct
// So Another proposer gets paid Disputer's bond
vm.warp(_expectedDeadline + 2 days);
vm.warp(block.timestamp + _expectedDeadline + 2 days);
_mockArbitrator.setAnswer(IOracle.DisputeStatus.Lost);
oracle.resolveDispute(mockRequest, _secondResponse, _secondDispute);

Expand Down Expand Up @@ -340,7 +340,7 @@ contract Integration_BondEscalation is IntegrationBase {

// Step 4: Disputer runs out of capital
// Step 5: The tying buffer kicks in
vm.warp(_bondEscalationDeadline + 1);
vm.warp(block.timestamp + _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);
Expand Down Expand Up @@ -420,7 +420,7 @@ contract Integration_BondEscalation is IntegrationBase {
_bondEscalationModule.pledgeForDispute(mockRequest, mockDispute);

// Step 6: Proposer loses in resolution
vm.warp(_bondEscalationDeadline + 1);
vm.warp(block.timestamp + _bondEscalationDeadline + 1);
oracle.escalateDispute(mockRequest, mockResponse, mockDispute);
oracle.resolveDispute(mockRequest, mockResponse, mockDispute);

Expand Down Expand Up @@ -470,7 +470,7 @@ contract Integration_BondEscalation is IntegrationBase {

// Step 4: Disputer runs out of capital
// Step 5: The tying buffer kicks in
vm.warp(_bondEscalationDeadline + 1);
vm.warp(block.timestamp + _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);
Expand Down Expand Up @@ -531,7 +531,7 @@ contract Integration_BondEscalation is IntegrationBase {

// Step 4: Disputer runs out of capital
// Step 5: The tying buffer kicks in
vm.warp(_bondEscalationDeadline + 1);
vm.warp(block.timestamp + _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);
Expand Down
2 changes: 1 addition & 1 deletion solidity/test/integration/EscalateDispute.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ contract Integration_EscalateDispute is IntegrationBase {
);

// We escalate the dispute
_mineBlocks(_blocksDeadline + 1);
vm.warp(block.timestamp + _expectedDeadline + 1);
oracle.escalateDispute(mockRequest, mockResponse, mockDispute);

// We check that the dispute was escalated
Expand Down
6 changes: 3 additions & 3 deletions solidity/test/integration/Finalization.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ contract Integration_Finalization is IntegrationBase {
_proposeResponse();

// Traveling to the end of the dispute window
vm.warp(_expectedDeadline + 1 + _baseDisputeWindow);
vm.warp(block.timestamp + _expectedDeadline + 1 + _baseDisputeWindow);

// Check: all external calls are made?
vm.expectCall(address(_mockCallback), abi.encodeWithSelector(IProphetCallback.prophetCallback.selector, _calldata));
Expand Down Expand Up @@ -70,7 +70,7 @@ contract Integration_Finalization is IntegrationBase {
* @notice Finalizing a request with a ongoing dispute reverts.
*/
function test_revertFinalizeInDisputeWindow(uint256 _timestamp) public {
_timestamp = bound(_timestamp, block.timestamp, _expectedDeadline - _baseDisputeWindow - 1);
_timestamp = bound(_timestamp, block.timestamp, block.timestamp + _expectedDeadline - _baseDisputeWindow - 1);

_createRequest();
_proposeResponse();
Expand All @@ -96,7 +96,7 @@ contract Integration_Finalization is IntegrationBase {
_proposeResponse();

// Traveling to the end of the dispute window
vm.warp(_expectedDeadline + 1 + _baseDisputeWindow);
vm.warp(block.timestamp + _expectedDeadline + 1 + _baseDisputeWindow);

vm.expectCall(address(_mockCallback), abi.encodeWithSelector(IProphetCallback.prophetCallback.selector, _calldata));
vm.prank(_finalizer);
Expand Down
8 changes: 2 additions & 6 deletions solidity/test/integration/IntegrationBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,11 @@ contract IntegrationBase is DSTestPlus, TestConstants, Helpers {
string internal _expectedResponse = '{"ethereum":{"usd":1000}}';
uint256 internal _expectedBondSize = 100 ether;
uint256 internal _expectedReward = 30 ether;
uint256 internal _expectedDeadline;
uint256 internal _expectedCallbackValue = 42;
uint256 internal _baseDisputeWindow = 120 * BLOCK_TIME;
bytes32 internal _ipfsHash = bytes32('QmR4uiJH654k3Ta2uLLQ8r');
uint256 internal _blocksDeadline = 600;
uint256 internal _timestampDeadline = _blocksDeadline * BLOCK_TIME;
uint256 internal _baseDisputeWindow = 6 hours;
uint256 internal _expectedDeadline = 10 days;

function setUp() public virtual {
vm.createSelectFork(vm.rpcUrl('optimism'), FORK_BLOCK);
Expand Down Expand Up @@ -135,9 +134,6 @@ contract IntegrationBase is DSTestPlus, TestConstants, Helpers {
_mockArbitrator = new MockArbitrator();
vm.stopPrank();

// Set the expected deadline
_expectedDeadline = block.timestamp + _timestampDeadline;

// Configure the mock request
mockRequest.requestModuleData = abi.encode(
IHttpRequestModule.RequestParameters({
Expand Down
4 changes: 2 additions & 2 deletions solidity/test/integration/Payments.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ contract Integration_Payments is IntegrationBase {
assertEq(_accountingExtension.bondedAmountOf(proposer, usdc, _requestId), _bondSize);

// Warp to finalization time.
vm.warp(_expectedDeadline + _baseDisputeWindow);
vm.warp(block.timestamp + _expectedDeadline + _baseDisputeWindow);

// Finalize request/response
oracle.finalize(mockRequest, mockResponse);
Expand Down Expand Up @@ -66,7 +66,7 @@ contract Integration_Payments is IntegrationBase {
assertEq(_accountingExtension.bondedAmountOf(proposer, weth, _requestId), _bondSize);

// Warp to finalization time.
vm.warp(_expectedDeadline + _baseDisputeWindow);
vm.warp(block.timestamp + _expectedDeadline + _baseDisputeWindow);
// Finalize request/response.
oracle.finalize(mockRequest, mockResponse);

Expand Down
2 changes: 1 addition & 1 deletion solidity/test/integration/ResponseDispute.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ contract Integration_ResponseDispute is IntegrationBase {
* @notice Disputing a finalized response should revert
*/
function test_disputeResponse_alreadyFinalized() public {
vm.warp(_expectedDeadline + _baseDisputeWindow);
vm.warp(block.timestamp + _expectedDeadline + _baseDisputeWindow);
oracle.finalize(mockRequest, mockResponse);

vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _getId(mockRequest)));
Expand Down
6 changes: 3 additions & 3 deletions solidity/test/integration/ResponseProposal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ contract Integration_ResponseProposal is IntegrationBase {
/**
* @notice Proposing a response after the deadline reverts
*/
function test_proposeResponse_afterDeadline(uint256 _timestamp, bytes memory _responseBytes) public {
vm.assume(_timestamp > _expectedDeadline);
function test_proposeResponse_afterDeadline(uint256 _secondsAfter, bytes memory _responseBytes) public {
_secondsAfter = bound(_secondsAfter, 1, type(uint248).max);

// Warp to timestamp after deadline
vm.warp(_timestamp);
vm.warp(block.timestamp + _expectedDeadline + _secondsAfter);

mockResponse.response = _responseBytes;

Expand Down
2 changes: 1 addition & 1 deletion solidity/test/integration/RootVerification.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ contract Integration_RootVerification is IntegrationBase {
vm.prank(proposer);
oracle.proposeResponse(mockRequest, mockResponse);

vm.warp(_expectedDeadline + _baseDisputeWindow);
vm.warp(block.timestamp + _expectedDeadline + _baseDisputeWindow);

oracle.finalize(mockRequest, mockResponse);
}
Expand Down
Loading

0 comments on commit b88fda6

Please sign in to comment.