Skip to content

Commit

Permalink
test(medusa): requester properties (#57)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes GRT-XXX

---------

Co-authored-by: drgorillamd <83670532+drgorillamd@users.noreply.github.com>
Co-authored-by: Simon Something /DrGoNoGo <83670532+simon-something@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 28, 2024
1 parent c0ffeee commit 9c8dce2
Show file tree
Hide file tree
Showing 24 changed files with 680 additions and 284 deletions.
2 changes: 1 addition & 1 deletion .solhint.tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"var-name-mixedcase": "off",
"const-name-snakecase": "off",
"no-inline-assembly": "off",
"no-empty-blocks": "error",
"no-empty-blocks": "off",
"definition-name-capwords": "off",
"named-parameters-function": "off",
"no-global-import": "off",
Expand Down
4 changes: 2 additions & 2 deletions medusa.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"corpusDirectory": "test/invariants/corpus",
"coverageEnabled": true,
"targetContracts": ["FuzzTest"],
"predeployedContracts": {},
"predeployedContracts": { "ValidatorLib": "0xc0ffee" },
"targetContractsBalances": [],
"constructorArgs": {},
"deployerAddress": "0x30000",
Expand Down Expand Up @@ -76,7 +76,7 @@
"target": "test/invariants/FuzzTest.t.sol",
"solcVersion": "",
"exportDirectory": "",
"args": ["--compile-libraries=(ValidatorLib,0xf0)"]
"args": ["--compile-libraries=(ValidatorLib,0xc0ffee)"]
}
},
"logging": {
Expand Down
5 changes: 2 additions & 3 deletions test/invariants/FuzzTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity 0.8.26;
import {PropertyParent} from './properties/PropertyParent.t.sol';

contract FuzzTest is PropertyParent {
function test_debug() public {
this.property_sanityCheck();
}
//solhint-disable no-empty-blocks
function test_debug() public {}
}
108 changes: 103 additions & 5 deletions test/invariants/Setup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ contract Setup is Utils {
IEpochManager internal epochManager;

// Constants
uint256 internal constant START_EPOCH = 1000;
uint256 internal immutable START_EPOCH = block.timestamp;
uint64 internal constant MIN_THAWING_PERIOD = 1 days;
uint128 internal constant INITIAL_MAX_USERS_TO_CHECK = 10;
uint32 internal constant MAX_VERIFIER_CUT = 1_000_000;
Expand All @@ -88,10 +88,12 @@ contract Setup is Utils {
uint256 internal constant TYING_BUFFER = 3 days;
uint256 internal constant DISPUTE_DISPUTE_WINDOW = 2 weeks;

string[] internal INITIAL_CHAINS = ['mainnet', 'optimism', 'arbitrum'];

constructor() {
// Deploy mock contracts
GRT = IERC20(address(new FuzzERC20()));
epochManager = IEpochManager(address(new MockEpochManager(START_EPOCH)));
epochManager = IEpochManager(address(new MockEpochManager()));
horizonStaking = IHorizonStaking(address(new MockHorizonStaking(GRT)));

// Deploy core contracts
Expand Down Expand Up @@ -157,12 +159,15 @@ contract Setup is Utils {
horizonAccountingExtension.approveModule(address(bondEscalationModule));

// Set up initial chain IDs
eboRequestCreator.addChain('mainnet');
eboRequestCreator.addChain('optimism');
eboRequestCreator.addChain('arbitrum');
for (uint256 i; i < INITIAL_CHAINS.length; i++) {
eboRequestCreator.addChain(INITIAL_CHAINS[i]);
}

// Set up initial module parameters
_setupModuleParameters();

// System health check
_sanityCheck();
}

function _setupModuleParameters() internal {
Expand Down Expand Up @@ -205,4 +210,97 @@ contract Setup is Utils {
// Set finality module
eboRequestCreator.setFinalityModuleData(address(eboFinalityModule));
}

/// @custom:property-id 0
/// @custom:property Check if eboRequestCreator has the correct properties, including the ones set after its initial deployment
function _sanityCheck() internal {
assertEq(address(eboRequestCreator.ORACLE()), address(oracle), 'prop-0: wrong oracle address');

assertEq(address(eboRequestCreator.ARBITRABLE()), address(arbitrable), 'prop-0: wrong arbitrable address');

assertEq(eboRequestCreator.START_EPOCH(), START_EPOCH, 'prop-0: wrong start epoch');

assertEq(address(eboRequestCreator.epochManager()), address(epochManager), 'prop-0: wrong epoch manager address');

IOracle.Request memory initialRequestStored = eboRequestCreator.getRequestData();

assertEq(
initialRequestStored.requestModule, address(eboRequestModule), 'prop-0: wrong request module in initialRequest'
);

assertEq(
initialRequestStored.responseModule,
address(bondedResponseModule),
'prop-0: wrong response module in initialRequest'
);

assertEq(
initialRequestStored.disputeModule, address(bondEscalationModule), 'prop-0: wrong disputeModule in initialRequest'
);

assertEq(
initialRequestStored.resolutionModule,
address(arbitratorModule),
'prop-0: wrong resolutionModule in initialRequest'
);

assertEq(
initialRequestStored.finalityModule, address(eboFinalityModule), 'prop-0: wrong finalityModule in initialRequest'
);

assertEq(initialRequestStored.requester, address(eboRequestCreator), 'prop-0: wrong requester in initialRequest');

assertEq(
initialRequestStored.requestModuleData,
abi.encode(
IEBORequestModule.RequestParameters({
epoch: START_EPOCH,
chainId: 'mainnet',
accountingExtension: IAccountingExtension(address(horizonAccountingExtension)),
paymentAmount: PAYMENT_AMOUNT
})
),
'prop-0: wrong requestModuleData in initialRequest'
);

assertEq(
initialRequestStored.responseModuleData,
abi.encode(
IBondedResponseModule.RequestParameters({
accountingExtension: IAccountingExtension(address(horizonAccountingExtension)),
bondToken: GRT,
bondSize: RESPONSE_BOND_SIZE,
deadline: RESPONSE_DEADLINE,
disputeWindow: RESPONSE_DISPUTE_WINDOW
})
),
'prop-0: wrong responseModuleData module data in initialRequest'
);

assertEq(
initialRequestStored.disputeModuleData,
abi.encode(
IBondEscalationModule.RequestParameters({
accountingExtension: IBondEscalationAccounting(address(horizonAccountingExtension)),
bondToken: GRT,
bondSize: DISPUTE_BOND_SIZE,
maxNumberOfEscalations: MAX_NB_ESCALATION,
bondEscalationDeadline: DISPUTE_DEADLINE,
tyingBuffer: TYING_BUFFER,
disputeWindow: DISPUTE_DISPUTE_WINDOW
})
),
'prop-0: wrong disputeModuleData in initialRequest'
);

assertEq(
initialRequestStored.resolutionModuleData,
abi.encode(IArbitratorModule.RequestParameters({arbitrator: address(councilArbitrator)})),
'prop-0: wrong resolutionModuleData in initialRequest'
);

assertEq(initialRequestStored.finalityModuleData, bytes(''), 'prop-0: wrong finality module data in initialRequest');

assertEq(initialRequestStored.nonce, 0, 'prop-0: wrong nonce in initialRequest');
}
}
66 changes: 41 additions & 25 deletions test/invariants/handlers/BaseHandler.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ 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 the chainId->chain (reverse the hash)
mapping(bytes32 _hash => string _chain) internal _ghost_chainIdToChain;

// Track request details
mapping(bytes32 _requestId => IOracle.Request _data) internal _ghost_requestData;
Expand All @@ -20,53 +25,64 @@ contract BaseHandler is Setup, Actors {
// Track disputes
mapping(bytes32 _requestId => bytes32[] _disputeIds) internal _ghost_disputes; // requestId => disputeIds
mapping(bytes32 _disputeId => IOracle.Dispute _data) internal _ghost_disputeData;
mapping(bytes32 _disputeId => bool _escalated) internal _ghost_escalatedDisputes;

// Track which requests came from EBORequestCreator
mapping(bytes32 _requestId => bool _isFromRequestCreator) internal _ghost_validRequests;

// Track chain IDs used per epoch to prevent duplicates
mapping(uint256 _epoch => mapping(string _chainId => bool _isRequested)) internal _ghost_epochChainIds;

// Track bonds and pledges
mapping(address _owner => mapping(bytes32 _requestId => uint256 _boundedAmount)) internal _ghost_bonds;
mapping(address _pledger => mapping(bytes32 _disputeId => uint256 _pledgedAmount)) internal _ghost_pledgesFor;
mapping(address _pledger => mapping(bytes32 _disputeId => uint256 _pledgedAmount)) internal _ghost_pledgesAgainst;

// Helper functions
function _boundEpoch(uint256 _epoch) internal view returns (uint256) {
return bound(_epoch, START_EPOCH, START_EPOCH + 1000);
}

function _boundAmount(uint256 _amount) internal pure returns (uint256) {
return bound(_amount, 1, 1_000_000e18);
}
function _getRandomChain(uint256 _seed) internal view returns (string memory) {
bytes32[] memory chainIds = eboRequestCreator.getAllowedChainIds();
if (chainIds.length == 0) return '';

function _boundBlockNumber(uint256 _blockNumber) internal view returns (uint256) {
return bound(_blockNumber, block.number - 1000, block.number);
return _ghost_chainIdToChain[chainIds[_seed % chainIds.length]];
}

function _generateChainId(uint256 _seed) internal pure returns (string memory) {
string[3] memory chains = ['mainnet', 'optimism', 'arbitrum'];
return chains[_seed % 3];
}

function _getRandomRequest(uint256 _seed) internal view returns (bytes32) {
if (_ghost_requests.length == 0) return bytes32(0);
return _ghost_requests[_seed % _ghost_requests.length];
function _getRandomRequest(uint256 _seed) internal view returns (bytes32, IOracle.Request memory) {
if (_ghost_requests.length == 0) {
return (
bytes32(0),
IOracle.Request(
0,
address(0),
address(0),
address(0),
address(0),
address(0),
address(0),
bytes(''),
bytes(''),
bytes(''),
bytes(''),
bytes('')
)
);
}
bytes32 requestId = _ghost_requests[_seed % _ghost_requests.length];
return (requestId, _ghost_requestData[requestId]);
}

function _getRandomActiveResponse(bytes32 _requestId, uint256 _seed) internal view returns (IOracle.Response memory) {
function _getRandomActiveResponse(
bytes32 _requestId,
uint256 _seed
) internal view returns (bytes32, IOracle.Response memory) {
bytes32 responseId = _ghost_activeResponses[_requestId][_seed % _ghost_activeResponses[_requestId].length];
return _ghost_responseData[responseId];

return (responseId, _ghost_responseData[responseId]);
}

function _getRandomDispute(bytes32 _requestId, uint256 _seed) internal view returns (IOracle.Dispute memory) {
function _getRandomDispute(bytes32 _requestId, uint256 _seed) internal view returns (bytes32, IOracle.Dispute memory) {
bytes32[] storage disputes = _ghost_disputes[_requestId];
if (disputes.length == 0) {
return IOracle.Dispute(address(0), address(0), bytes32(0), 0);
return (bytes32(0), IOracle.Dispute(address(0), address(0), bytes32(0), 0));
}
bytes32 disputeId = disputes[_seed % disputes.length];
return _ghost_disputeData[disputeId];
return (disputeId, _ghost_disputeData[disputeId]);
}

// Events to track state changes
Expand Down
22 changes: 9 additions & 13 deletions test/invariants/handlers/HandlerBondEscalationModule.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,47 @@ 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, IOracle.Request memory request) = _getRandomRequest(_requestSeed);
if (requestId == bytes32(0)) return;

IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex);
(bytes32 disputeId, IOracle.Dispute memory dispute) = _getRandomDispute(requestId, _disputeIndex);
if (dispute.requestId == bytes32(0)) return;

if (oracle.disputeStatus(keccak256(abi.encode(dispute))) != IOracle.DisputeStatus.Escalated) return;

address pledger = _pickActor(_actorSeed);
bondEscalationModule.pledgeForDispute(_ghost_requestData[requestId], dispute);
bondEscalationModule.pledgeForDispute(request, dispute);

bytes32 disputeId = keccak256(abi.encode(dispute));
_ghost_pledgesFor[pledger][disputeId] += DISPUTE_BOND_SIZE;
_ghost_bonds[pledger][requestId] += DISPUTE_BOND_SIZE; // Track bond amount
}

function handlePledgeAgainstDispute(uint256 _requestSeed, uint256 _disputeIndex, uint256 _actorSeed) external {
bytes32 requestId = _getRandomRequest(_requestSeed);
(bytes32 requestId, IOracle.Request memory request) = _getRandomRequest(_requestSeed);
if (requestId == bytes32(0)) return;

IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex);
(bytes32 disputeId, IOracle.Dispute memory dispute) = _getRandomDispute(requestId, _disputeIndex);
if (dispute.requestId == bytes32(0)) return;

if (oracle.disputeStatus(keccak256(abi.encode(dispute))) != IOracle.DisputeStatus.Escalated) return;

address pledger = _pickActor(_actorSeed);
bondEscalationModule.pledgeAgainstDispute(_ghost_requestData[requestId], dispute);
bondEscalationModule.pledgeAgainstDispute(request, dispute);

bytes32 disputeId = keccak256(abi.encode(dispute));
_ghost_pledgesAgainst[pledger][disputeId] += DISPUTE_BOND_SIZE;
_ghost_bonds[pledger][requestId] += DISPUTE_BOND_SIZE; // Track bond amount
}

function handleSettleBondEscalation(uint256 _requestSeed, uint256 _disputeIndex) external {
bytes32 requestId = _getRandomRequest(_requestSeed);
(bytes32 requestId, IOracle.Request memory request) = _getRandomRequest(_requestSeed);
if (requestId == bytes32(0)) return;

IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex);
(bytes32 disputeId, IOracle.Dispute memory dispute) = _getRandomDispute(requestId, _disputeIndex);
if (dispute.requestId == bytes32(0)) return;

IOracle.Response memory response = _ghost_responseData[dispute.responseId];
bondEscalationModule.settleBondEscalation(_ghost_requestData[requestId], response, dispute);
bondEscalationModule.settleBondEscalation(request, response, dispute);

// Update ghost variables
bytes32 disputeId = keccak256(abi.encode(dispute));
// Clear dispute pledges as they're now settled
delete _ghost_pledgesFor[dispute.disputer][disputeId];
delete _ghost_pledgesAgainst[dispute.disputer][disputeId];
Expand Down
7 changes: 3 additions & 4 deletions test/invariants/handlers/HandlerBondedResponseModule.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@ import {BaseHandler, IOracle} from './BaseHandler.t.sol';

contract HandlerBondedResponseModule is BaseHandler {
function handleReleaseUnutilizedResponse(uint256 _requestSeed) external {
bytes32 requestId = _getRandomRequest(_requestSeed);
(bytes32 requestId, IOracle.Request memory request) = _getRandomRequest(_requestSeed);
if (requestId == bytes32(0)) return;

IOracle.Response memory response = _getRandomActiveResponse(requestId, _requestSeed);
(bytes32 responseId, IOracle.Response memory response) = _getRandomActiveResponse(requestId, _requestSeed);
if (response.requestId == bytes32(0)) return;

bondedResponseModule.releaseUnutilizedResponse(_ghost_requestData[requestId], response);
bondedResponseModule.releaseUnutilizedResponse(request, response);

// Update ghost variables - clear response state
bytes32 responseId = keccak256(abi.encode(response));
delete _ghost_activeResponses[requestId];
delete _ghost_responseData[responseId];
delete _ghost_bonds[response.proposer][requestId];
Expand Down
6 changes: 2 additions & 4 deletions test/invariants/handlers/HandlerCouncilArbitrator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ 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,) = _getRandomRequest(_requestSeed);
if (requestId == bytes32(0)) return;

IOracle.Dispute memory dispute = _getRandomDispute(requestId, _disputeIndex);
(bytes32 disputeId, IOracle.Dispute memory dispute) = _getRandomDispute(requestId, _disputeIndex);
if (dispute.requestId == bytes32(0)) return;

bytes32 disputeId = keccak256(abi.encode(dispute));

// Generate a valid dispute status (NoResolution, Won, Lost)
IOracle.DisputeStatus status = IOracle.DisputeStatus(
bound(_statusSeed, uint8(IOracle.DisputeStatus.NoResolution), uint8(IOracle.DisputeStatus.Lost))
Expand Down
6 changes: 3 additions & 3 deletions test/invariants/handlers/HandlerEBOFinalityModule.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import {BaseHandler, IEBORequestCreator, IOracle} from './BaseHandler.t.sol';
contract HandlerEBOFinalityModule is BaseHandler {
function handleAmendEpoch(
uint256 _epoch,
uint256 _blockNumber,
uint256[] calldata _chainIdSeeds,
uint256[] calldata _blockNumbers
) external {
if (_chainIdSeeds.length != _blockNumbers.length) return;

_epoch = _boundEpoch(_epoch);
string[] memory chainIds = new string[](_chainIdSeeds.length);
uint256[] memory blockNums = new uint256[](_blockNumbers.length);

for (uint256 i = 0; i < _chainIdSeeds.length; i++) {
chainIds[i] = _generateChainId(_chainIdSeeds[i]);
blockNums[i] = _boundBlockNumber(_blockNumbers[i]);
chainIds[i] = _getRandomChain(_chainIdSeeds[i]);
blockNums[i] = _blockNumber;
}

eboFinalityModule.amendEpoch(_epoch, chainIds, blockNums);
Expand Down
Loading

0 comments on commit 9c8dce2

Please sign in to comment.