diff --git a/test/invariants/handlers/BaseHandler.t.sol b/test/invariants/handlers/BaseHandler.t.sol index da1edae..c6b016d 100644 --- a/test/invariants/handlers/BaseHandler.t.sol +++ b/test/invariants/handlers/BaseHandler.t.sol @@ -8,6 +8,8 @@ import {Actors} from '../helpers/Actors.t.sol'; contract BaseHandler is Setup, Actors { // Track all created request IDs bytes32[] internal _ghost_requests; + // Track all created request IDs per epoch and chainId + mapping(uint256 _epoch => mapping(string _chainId => bytes32[] _requestIds)) internal _ghost_requestsPerEpochChainId; // Track request details mapping(bytes32 _requestId => IOracle.Request _data) internal _ghost_requestData; @@ -33,7 +35,7 @@ contract BaseHandler is Setup, Actors { mapping(address _pledger => mapping(bytes32 _disputeId => uint256 _pledgedAmount)) internal _ghost_pledgesAgainst; // Helper functions - function _boundEpoch(uint256 _epoch) internal view returns (uint256) { + function _boundEpoch(uint256 _epoch) internal pure returns (uint256) { return bound(_epoch, START_EPOCH, START_EPOCH + 1000); } @@ -50,7 +52,7 @@ contract BaseHandler is Setup, Actors { return chains[_seed % 3]; } - function _getRandomRequest(uint256 _seed) internal view returns (bytes32) { + function _getRandomRequestId(uint256 _seed) internal view returns (bytes32) { if (_ghost_requests.length == 0) return bytes32(0); return _ghost_requests[_seed % _ghost_requests.length]; } diff --git a/test/invariants/handlers/HandlerBondEscalationModule.t.sol b/test/invariants/handlers/HandlerBondEscalationModule.t.sol index 3861603..25aa180 100644 --- a/test/invariants/handlers/HandlerBondEscalationModule.t.sol +++ b/test/invariants/handlers/HandlerBondEscalationModule.t.sol @@ -5,7 +5,7 @@ import {BaseHandler, IOracle} from './BaseHandler.t.sol'; contract HandlerBondEscalationModule is BaseHandler { function handlePledgeForDispute(uint256 _requestSeed, uint256 _disputeIndex, uint256 _actorSeed) external { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex); @@ -22,7 +22,7 @@ contract HandlerBondEscalationModule is BaseHandler { } function handlePledgeAgainstDispute(uint256 _requestSeed, uint256 _disputeIndex, uint256 _actorSeed) external { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex); @@ -39,7 +39,7 @@ contract HandlerBondEscalationModule is BaseHandler { } function handleSettleBondEscalation(uint256 _requestSeed, uint256 _disputeIndex) external { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex); diff --git a/test/invariants/handlers/HandlerBondedResponseModule.t.sol b/test/invariants/handlers/HandlerBondedResponseModule.t.sol index 7a1822b..3665a66 100644 --- a/test/invariants/handlers/HandlerBondedResponseModule.t.sol +++ b/test/invariants/handlers/HandlerBondedResponseModule.t.sol @@ -5,7 +5,7 @@ import {BaseHandler, IOracle} from './BaseHandler.t.sol'; contract HandlerBondedResponseModule is BaseHandler { function handleReleaseUnutilizedResponse(uint256 _requestSeed) external { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; IOracle.Response memory response = _getRandomActiveResponse(requestId, _requestSeed); diff --git a/test/invariants/handlers/HandlerCouncilArbitrator.t.sol b/test/invariants/handlers/HandlerCouncilArbitrator.t.sol index ed08aa2..59c7707 100644 --- a/test/invariants/handlers/HandlerCouncilArbitrator.t.sol +++ b/test/invariants/handlers/HandlerCouncilArbitrator.t.sol @@ -5,7 +5,7 @@ import {BaseHandler, IOracle} from './BaseHandler.t.sol'; contract HandlerCouncilArbitrator is BaseHandler { function handleArbitrateDispute(uint256 _requestSeed, uint256 _disputeIndex, uint256 _statusSeed) external { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex); diff --git a/test/invariants/handlers/HandlerEBORequestCreator.t.sol b/test/invariants/handlers/HandlerEBORequestCreator.t.sol index 74780b5..5bda157 100644 --- a/test/invariants/handlers/HandlerEBORequestCreator.t.sol +++ b/test/invariants/handlers/HandlerEBORequestCreator.t.sol @@ -16,9 +16,6 @@ contract HandlerEBORequestCreator is BaseHandler { _epoch = _boundEpoch(_epoch); string memory chainId = _generateChainId(_chainIdSeed); - // Prevent duplicate chainId for same epoch - if (_ghost_epochChainIds[_epoch][chainId]) return; - // Create request via EBORequestCreator eboRequestCreator.createRequest(_epoch, chainId); @@ -37,6 +34,7 @@ contract HandlerEBORequestCreator is BaseHandler { // Track the request _ghost_requests.push(requestId); + _ghost_requestsPerEpochChainId[_epoch][chainId].push(requestId); _ghost_requestData[requestId] = requestData; _ghost_validRequests[requestId] = true; _ghost_epochChainIds[_epoch][chainId] = true; diff --git a/test/invariants/handlers/HandlerHorizonAccountingExtension.t.sol b/test/invariants/handlers/HandlerHorizonAccountingExtension.t.sol index 5d1abde..3f805c0 100644 --- a/test/invariants/handlers/HandlerHorizonAccountingExtension.t.sol +++ b/test/invariants/handlers/HandlerHorizonAccountingExtension.t.sol @@ -16,7 +16,7 @@ contract HandlerHorizonAccountingExtension is BaseHandler { function handlePledge(uint256 _pledgerSeed, uint256 _requestSeed, uint256 _disputeIndex, uint256 _amount) external { address pledger = _pickActor(_pledgerSeed); - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex); @@ -32,7 +32,7 @@ contract HandlerHorizonAccountingExtension is BaseHandler { uint256 _amountPerPledger, uint256 _winningPledgersLength ) external { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex); @@ -45,7 +45,7 @@ contract HandlerHorizonAccountingExtension is BaseHandler { } function handleClaimEscalationReward(uint256 _requestSeed, uint256 _disputeIndex, uint256 _actorSeed) external { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex); @@ -68,7 +68,7 @@ contract HandlerHorizonAccountingExtension is BaseHandler { uint256 _usersToSlash, uint256 _maxUsersToCheck ) external { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex); diff --git a/test/invariants/handlers/HandlerOracle.t.sol b/test/invariants/handlers/HandlerOracle.t.sol index 0714b44..f1ba3c9 100644 --- a/test/invariants/handlers/HandlerOracle.t.sol +++ b/test/invariants/handlers/HandlerOracle.t.sol @@ -5,7 +5,7 @@ import {BaseHandler, IOracle} from './BaseHandler.t.sol'; contract HandlerOracle is BaseHandler { function handleCreateRequest(uint256 _requestSeed, bytes32 _previousId) external returns (bytes32) { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return bytes32(0); return oracle.createRequest(_ghost_requestData[requestId], _previousId); @@ -16,7 +16,7 @@ contract HandlerOracle is BaseHandler { uint256 _blockNumber, uint256 _actorSeed ) external returns (bytes32) { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0) || !_ghost_validRequests[requestId]) { return bytes32(0); } @@ -40,7 +40,7 @@ contract HandlerOracle is BaseHandler { } function handleDisputeResponseOracle(uint256 _requestSeed, uint256 _actorSeed) external returns (bytes32) { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0) || !_ghost_validRequests[requestId]) { return bytes32(0); } @@ -70,7 +70,7 @@ contract HandlerOracle is BaseHandler { } function handleEscalateDispute(uint256 _requestSeed, uint256 _disputeIndex) external { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex); @@ -83,7 +83,7 @@ contract HandlerOracle is BaseHandler { } function handleResolveDispute(uint256 _requestSeed, uint256 _disputeIndex) external { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex); @@ -94,7 +94,7 @@ contract HandlerOracle is BaseHandler { } function handleFinalize(uint256 _requestSeed) external { - bytes32 requestId = _getRandomRequest(_requestSeed); + bytes32 requestId = _getRandomRequestId(_requestSeed); if (requestId == bytes32(0)) return; if (_ghost_activeResponses[requestId].length == 0) return; diff --git a/test/invariants/helpers/Utils.t.sol b/test/invariants/helpers/Utils.t.sol index b74fbd4..846fdb7 100644 --- a/test/invariants/helpers/Utils.t.sol +++ b/test/invariants/helpers/Utils.t.sol @@ -68,6 +68,17 @@ contract Utils { } } + function assertNotEq(uint256 a, uint256 b) internal { + assertNotEq(a, b, 'assertNotEq: a == b'); + } + + function assertNotEq(uint256 a, uint256 b, string memory reason) internal { + if (a == b) { + emit TestFailure(reason); + assert(false); + } + } + function assertEq(address a, address b) internal { assertEq(a, b, 'assertEq: a != b'); } @@ -79,6 +90,17 @@ contract Utils { } } + function assertNotEq(address a, address b) internal { + assertNotEq(a, b, 'assertNotEq: a == b'); + } + + function assertNotEq(address a, address b, string memory reason) internal { + if (a == b) { + emit TestFailure(reason); + assert(false); + } + } + function assertEq(bytes memory a, bytes memory b) internal { assertEq(a, b, 'assertEq: a != b'); } @@ -96,6 +118,22 @@ contract Utils { } } + function assertNotEq(bytes memory a, bytes memory b) internal { + assertNotEq(a, b, 'assertNotEq: a == b'); + } + + function assertNotEq(bytes memory a, bytes memory b, string memory reason) internal { + if (a.length == b.length) { + for (uint256 i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return; + } + } + emit TestFailure(reason); + assert(false); + } + } + function assertTrue(bool a) internal { assertTrue(a, 'assertTrue: !a'); } diff --git a/test/invariants/properties/PropertyEBORequestCreator.t.sol b/test/invariants/properties/PropertyEBORequestCreator.t.sol index d212864..07e7c39 100644 --- a/test/invariants/properties/PropertyEBORequestCreator.t.sol +++ b/test/invariants/properties/PropertyEBORequestCreator.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; +import {IEBORequestModule, IOracle} from '../Setup.t.sol'; import {HandlerParent} from '../handlers/HandlerParent.t.sol'; contract PropertyEBORequestCreator is HandlerParent { @@ -10,9 +11,35 @@ contract PropertyEBORequestCreator is HandlerParent { /// @custom:property-id 1 /// @custom:property Requester can always create a request as long as the same chainId/epoch isn't requested yet function property_canAlwaysCreateRequest(uint256 _epoch, uint256 _chainIdSeed) external { - try HandlerParent(this).handleCreateRequest(_epoch, _chainIdSeed) {} + _epoch = _boundEpoch(_epoch); + string memory chainId = _generateChainId(_chainIdSeed); + + // Prevent duplicate chainId for same epoch + if (_ghost_epochChainIds[_epoch][chainId]) return; + + // Create request via EBORequestCreator + try eboRequestCreator.createRequest(_epoch, chainId) {} catch { assert(false); } } + + /// @custom:property-id 2 + /// @custom:property There can only be one active request per chainId/epoch at a time + function property_onlyOneActiveRequest(uint256 _requestIdSeed) external { + bytes32 requestId = _getRandomRequestId(_requestIdSeed); + if (requestId == bytes32(0)) return; + + IOracle.Request memory requestData = _ghost_requestData[requestId]; + IEBORequestModule.RequestParameters memory requestParams = + abi.decode(requestData.requestModuleData, (IEBORequestModule.RequestParameters)); + + uint256 requestsPerEpochChainId = _ghost_requestsPerEpochChainId[requestParams.epoch][requestParams.chainId].length; + if (requestsPerEpochChainId < 2) return; + + for (uint256 i; i < requestsPerEpochChainId - 1; ++i) { + requestId = _ghost_requestsPerEpochChainId[requestParams.epoch][requestParams.chainId][i]; + assertNotEq(oracle.finalizedAt(requestId), 0); + } + } }