diff --git a/contracts-abi/abi/MevCommitMiddleware.abi b/contracts-abi/abi/MevCommitMiddleware.abi index 2b7ad55d8..3109e6dbb 100644 --- a/contracts-abi/abi/MevCommitMiddleware.abi +++ b/contracts-abi/abi/MevCommitMiddleware.abi @@ -84,30 +84,6 @@ "outputs": [], "stateMutability": "nonpayable" }, - { - "type": "function", - "name": "executeSlashes", - "inputs": [ - { - "name": "blsPubkeys", - "type": "bytes[]", - "internalType": "bytes[]" - }, - { - "name": "slashIndexes", - "type": "uint256[]", - "internalType": "uint256[]" - } - ], - "outputs": [ - { - "name": "slashedAmounts", - "type": "uint256[]", - "internalType": "uint256[]" - } - ], - "stateMutability": "nonpayable" - }, { "type": "function", "name": "getLatestSlashAmount", @@ -1308,37 +1284,6 @@ ], "anonymous": false }, - { - "type": "event", - "name": "ValidatorSlashRequested", - "inputs": [ - { - "name": "blsPubkey", - "type": "bytes", - "indexed": false, - "internalType": "bytes" - }, - { - "name": "operator", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "vault", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "slashIndex", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, { "type": "event", "name": "ValidatorSlashed", diff --git a/contracts/contracts/interfaces/IMevCommitMiddleware.sol b/contracts/contracts/interfaces/IMevCommitMiddleware.sol index 724d2a948..a4b2656fe 100644 --- a/contracts/contracts/interfaces/IMevCommitMiddleware.sol +++ b/contracts/contracts/interfaces/IMevCommitMiddleware.sol @@ -86,9 +86,6 @@ interface IMevCommitMiddleware { /// @notice Emmitted when a validator record is deleted by the contract owner event ValRecordDeleted(bytes blsPubkey, address indexed msgSender); - /// @notice Emmitted when a validator slash is requested from a veto slasher - event ValidatorSlashRequested(bytes blsPubkey, address indexed operator, address indexed vault, uint256 slashIndex); - /// @notice Emmitted when a validator is slashed from an instant slasher event ValidatorSlashed(bytes blsPubkey, address indexed operator, address indexed vault, uint256 slashedAmount); diff --git a/contracts/contracts/validator-registry/middleware/MevCommitMiddleware.sol b/contracts/contracts/validator-registry/middleware/MevCommitMiddleware.sol index 8705a1038..e1731f0c7 100644 --- a/contracts/contracts/validator-registry/middleware/MevCommitMiddleware.sol +++ b/contracts/contracts/validator-registry/middleware/MevCommitMiddleware.sol @@ -265,27 +265,6 @@ contract MevCommitMiddleware is IMevCommitMiddleware, MevCommitMiddlewareStorage emit ValidatorPositionsSwapped(blsPubkeys, swappedVaults, swappedOperators, newPositions); } - /// @dev Allows the slash oracle to execute slashes for validators secured by vaults with veto slashers. - /// @notice See "Slash mechanics" in README.md for more details. - /// @param blsPubkeys BLS public keys corresponding to vaults with veto slashers. - /// @param slashIndexes Slash indexes obtained from ValidatorSlashRequested event emitted in _slashValidator. - /// @return slashedAmounts The actual amount of collateral slashed for each validator. - function executeSlashes(bytes[] calldata blsPubkeys, - uint256[] calldata slashIndexes) external onlySlashOracle returns (uint256[] memory slashedAmounts) { - - uint256 len = blsPubkeys.length; - require(len == slashIndexes.length, InvalidArrayLengths(len, slashIndexes.length)); - slashedAmounts = new uint256[](len); - for (uint256 i = 0; i < len; ++i) { - ValidatorRecord storage valRecord = validatorRecords[blsPubkeys[i]]; - address slasher = IVault(valRecord.vault).slasher(); - uint64 slasherType = IEntity(slasher).TYPE(); - require(slasherType == _VETO_SLASHER_TYPE, OnlyVetoSlashersRequireExecution(valRecord.vault, slasherType)); - slashedAmounts[i] = IVetoSlasher(slasher).executeSlash(slashIndexes[i], new bytes(0)); - } - return slashedAmounts; - } - /// @dev Pauses the contract, restricted to contract owner. function pause() external onlyOwner { _pause(); } @@ -563,15 +542,18 @@ contract MevCommitMiddleware is IMevCommitMiddleware, MevCommitMiddlewareStorage address slasher = IVault(valRecord.vault).slasher(); uint256 slasherType = IEntity(slasher).TYPE(); + uint256 slashedAmount; if (slasherType == _VETO_SLASHER_TYPE) { - uint256 slashIndex = IVetoSlasher(slasher).requestSlash( + IVetoSlasher vetoSlasher = IVetoSlasher(slasher); + uint256 slashIndex = vetoSlasher.requestSlash( _getSubnetwork(), valRecord.operator, amount, SafeCast.toUint48(captureTimestamp), new bytes(0)); - emit ValidatorSlashRequested(blsPubkey, valRecord.operator, valRecord.vault, slashIndex); + // Since resolver = address(0), slash can be executed immediately. + slashedAmount = vetoSlasher.executeSlash(slashIndex, new bytes(0)); } else if (slasherType == _INSTANT_SLASHER_TYPE) { - uint256 slashedAmount = ISlasher(slasher).slash( + slashedAmount = ISlasher(slasher).slash( _getSubnetwork(), valRecord.operator, amount, SafeCast.toUint48(captureTimestamp), new bytes(0)); - emit ValidatorSlashed(blsPubkey, valRecord.operator, valRecord.vault, slashedAmount); } + emit ValidatorSlashed(blsPubkey, valRecord.operator, valRecord.vault, slashedAmount); // If validator has not already requested deregistration, // do so to mark them as no longer opted-in. @@ -634,6 +616,7 @@ contract MevCommitMiddleware is IMevCommitMiddleware, MevCommitMiddlewareStorage } else if (delegator.TYPE() != _NETWORK_RESTAKE_DELEGATOR_TYPE) { revert UnknownDelegatorType(vault, delegator.TYPE()); } + IVaultStorage vaultContract = IVaultStorage(vault); uint256 vaultEpochDurationSeconds = vaultContract.epochDuration(); @@ -643,15 +626,14 @@ contract MevCommitMiddleware is IMevCommitMiddleware, MevCommitMiddlewareStorage if (slasherType == _VETO_SLASHER_TYPE) { IVetoSlasher vetoSlasher = IVetoSlasher(slasher); uint256 vetoDuration = vetoSlasher.vetoDuration(); - // For veto slashers, veto duration is repurposed as the phase in which the oracle can feasibly call `executeSlash`. - require(vetoDuration >= _MIN_VETO_DURATION, VetoDurationTooShort(vault, vetoDuration)); // Incorporate that veto duration will eat into portion of the epoch that oracle can feasibly request slashes. vaultEpochDurationSeconds -= vetoDuration; /// @dev No underflow possible, vetoDuration must be less than epochDuration as enforced by VetoSlasher.sol. - require(vetoSlasher.resolver(_getSubnetwork(), new bytes(0)) == address(0), - VetoSlasherMustHaveZeroResolver(vault)); + // Veto slasher must have a zero resolver s.t. slash can be executed immediately. + require(vetoSlasher.resolver(_getSubnetwork(), new bytes(0)) == address(0), VetoSlasherMustHaveZeroResolver(vault)); } else if (slasherType != _INSTANT_SLASHER_TYPE) { revert UnknownSlasherType(vault, slasherType); } + require(vaultEpochDurationSeconds > slashPeriodSeconds, InvalidVaultEpochDuration(vault, vaultEpochDurationSeconds, slashPeriodSeconds)); } diff --git a/contracts/contracts/validator-registry/middleware/MevCommitMiddlewareStorage.sol b/contracts/contracts/validator-registry/middleware/MevCommitMiddlewareStorage.sol index c61191415..a56f54688 100644 --- a/contracts/contracts/validator-registry/middleware/MevCommitMiddlewareStorage.sol +++ b/contracts/contracts/validator-registry/middleware/MevCommitMiddlewareStorage.sol @@ -22,11 +22,6 @@ abstract contract MevCommitMiddlewareStorage { /// @notice Enum TYPE for Symbiotic core VetoSlasher. uint64 internal constant _VETO_SLASHER_TYPE = 1; - /// @notice Minimum veto duration of 60 minutes for any vault. - /// @dev This is enforced because veto duration is repurposed as the min period in which the oracle can feasibly call `executeSlash`, - /// after initially requesting a slash. - uint256 internal constant _MIN_VETO_DURATION = 1 hours; - /// @notice Symbiotic core network registry. IRegistry public networkRegistry; diff --git a/contracts/contracts/validator-registry/middleware/README.md b/contracts/contracts/validator-registry/middleware/README.md index 8517d362a..b0cabd46f 100644 --- a/contracts/contracts/validator-registry/middleware/README.md +++ b/contracts/contracts/validator-registry/middleware/README.md @@ -117,13 +117,11 @@ Vaults with veto slashers: * must have an `epochDuration` greater than `slashPeriodSeconds` + `vetoDuration`, where `vetoDuration` is specified by the slasher. * require the resolver to be disabled via `address(0)`, since a permissioned oracle account invokes slashing, requiring only the most basic slashing interface. -Note in the context of the mev-commit network, `vetoDuration` is repurposed as the minimum (worst case) amount of time that the oracle has to call `executeSlash` after requesting a slash. `vetoDuration` for each vault must be greater than 60 minutes as enforced by the middleware contract. This means the oracle has at most `slashPeriodSeconds` to request a slash via `slashValidators`, and at least `vetoDuration` to call `executeSlash`. Note slash execution can happen anytime after a slash is requested, so long as less than `epochDuration` has passed since `captureTimestamp`. +The oracle has at most `slashPeriodSeconds` to request a slash via `slashValidators`. Both `VetoSlasher.requestSlash` and `VetoSlasher.executeSlash` get called during `slashValidators`. Upon the oracle successfully calling `slashValidators`, the middleware contract emits a `ValidatorSlashed` event. -Upon the oracle successfully calling `slashValidators`, the middleware contract emits one of two events for each slashed validator. `ValidatorSlashed` will be emitted for slashers with `INSTANT_SLASHER_TYPE`. `ValidatorSlashRequested` will be emitted for slashers with `VETO_SLASHER_TYPE`. +Read more about Symbiotic veto slashing [here](https://docs.symbiotic.fi/core-modules/vaults#veto-slashing). -If the slasher type is `VETO_SLASHER_TYPE`, the oracle is responsible for calling `executeSlashes`, AND this call must execute on L1 prior to the oracle calling `slashValidators` again. This ensures the oracle's subsequent calls to `slashValidators` will incorporate a properly decremented slashable stake from relevant Vaults. Read more about Symbiotic veto slashing [here](https://docs.symbiotic.fi/core-modules/vaults#veto-slashing). - -The described process considers Symbiotic's current design, where: +The described process considers Symbiotic's current design, where it's possible to execute a slash right after requesting one, if the resolver is disabled. See source below: 1. `requestSlash` must be called before `epochDuration` - `vetoDuration` (equal to `slashPeriodSeconds` for our network) has passed since `captureTimestamp`, [source](https://github.com/symbioticfi/core/blob/629b9faac2377a9eb9cfdc6362b30d1dc1ef48f2/src/contracts/slasher/VetoSlasher.sol#L93). 2. `executeSlash` must be called before `epochDuration` has passed since `captureTimestamp`, [source](https://github.com/symbioticfi/core/blob/629b9faac2377a9eb9cfdc6362b30d1dc1ef48f2/src/contracts/slasher/VetoSlasher.sol#L152). diff --git a/contracts/test/validator-registry/middleware/MevCommitMiddlewareTest.sol b/contracts/test/validator-registry/middleware/MevCommitMiddlewareTest.sol index 20ab8bf2e..91d2e583f 100644 --- a/contracts/test/validator-registry/middleware/MevCommitMiddlewareTest.sol +++ b/contracts/test/validator-registry/middleware/MevCommitMiddlewareTest.sol @@ -57,7 +57,6 @@ contract MevCommitMiddlewareTest is Test { event VaultSlashAmountUpdated(address indexed vault, uint160 slashAmount); event VaultDeregistrationRequested(address indexed vault); event VaultDeregistered(address indexed vault); - event ValidatorSlashRequested(bytes blsPubkey, address indexed operator, address indexed vault, uint256 slashIndex); event ValidatorSlashed(bytes blsPubkey, address indexed operator, address indexed vault, uint256 slashedAmount); event NetworkRegistrySet(address networkRegistry); event OperatorRegistrySet(address operatorRegistry); diff --git a/contracts/test/validator-registry/middleware/MevCommitMiddlewareTestCont.sol b/contracts/test/validator-registry/middleware/MevCommitMiddlewareTestCont.sol index 16fe966db..157982fcf 100644 --- a/contracts/test/validator-registry/middleware/MevCommitMiddlewareTestCont.sol +++ b/contracts/test/validator-registry/middleware/MevCommitMiddlewareTestCont.sol @@ -999,6 +999,11 @@ contract MevCommitMiddlewareTestCont is MevCommitMiddlewareTest { assertTrue(mevCommitMiddleware.isValidatorOptedIn(sampleValPubkey4)); assertTrue(mevCommitMiddleware.isValidatorOptedIn(sampleValPubkey5)); + // get stake amount before slashing + MockDelegator delegator = MockDelegator(vault2.delegator()); + uint256 allocatedStake = delegator.stake(bytes32("subnet"), operator1); + assertEq(allocatedStake, 99); + assertTrue(mevCommitMiddleware.isValidatorSlashable(sampleValPubkey4)); assertTrue(mevCommitMiddleware.isValidatorSlashable(sampleValPubkey5)); @@ -1016,11 +1021,15 @@ contract MevCommitMiddlewareTestCont is MevCommitMiddlewareTest { assertEq(slashRecord.numRegistered, 0); assertEq(slashRecord.numSlashed, 0); + assertEq(mevCommitMiddleware.potentialSlashableValidators(address(vault2), operator1), 1); + + uint256 slashAmount = mevCommitMiddleware.getLatestSlashAmount(address(vault2)); + vm.prank(slashOracle); vm.expectEmit(true, true, true, true); - emit ValidatorSlashRequested(sampleValPubkey4, operator1, address(vault2), 0); + emit ValidatorSlashed(sampleValPubkey4, operator1, address(vault2), slashAmount); vm.expectEmit(true, true, true, true); - emit ValidatorSlashRequested(sampleValPubkey5, operator1, address(vault2), 1); + emit ValidatorSlashed(sampleValPubkey5, operator1, address(vault2), slashAmount); vm.expectEmit(true, true, true, true); address[] memory expectedVaults = new address[](2); expectedVaults[0] = address(vault2); @@ -1044,11 +1053,15 @@ contract MevCommitMiddlewareTestCont is MevCommitMiddlewareTest { assertFalse(mevCommitMiddleware.isValidatorOptedIn(sampleValPubkey4)); assertFalse(mevCommitMiddleware.isValidatorOptedIn(sampleValPubkey5)); - // Validators should still be slashable, since veto slasher doesn't decrement stake immediately. - assertTrue(mevCommitMiddleware.isValidatorSlashable(sampleValPubkey4)); + // total stake should be 59 + allocatedStake = delegator.stake(bytes32("subnet"), operator1); + assertEq(allocatedStake, 59); + + // validator 4 should not be slashable, validator 5 should be since it's lower index + assertFalse(mevCommitMiddleware.isValidatorSlashable(sampleValPubkey4)); assertTrue(mevCommitMiddleware.isValidatorSlashable(sampleValPubkey5)); - assertEq(mevCommitMiddleware.potentialSlashableValidators(address(vault2), operator1), 1); + assertEq(mevCommitMiddleware.potentialSlashableValidators(address(vault2), operator1), 0); pos1 = mevCommitMiddleware.getPositionInValset(sampleValPubkey4, address(vault2), operator1); assertEq(pos1, 3); // final position of second set @@ -1070,52 +1083,7 @@ contract MevCommitMiddlewareTestCont is MevCommitMiddlewareTest { assertEq(slashRecord.numRegistered, 3); assertEq(slashRecord.numSlashed, 2); - MockDelegator delegator = MockDelegator(vault2.delegator()); - uint256 allocatedStake = delegator.stake(bytes32("subnet"), operator1); - assertEq(allocatedStake, 99); - - // Before slash execution, vault is colateralized in excess. - assertEq(mevCommitMiddleware.getNumSlashableVals(address(vault2), operator1), 4); - assertEq(mevCommitMiddleware.valsetLength(address(vault2), operator1), 3); - - vm.roll(block.number + 20); - - bytes[] memory blsPubkeys = new bytes[](2); - blsPubkeys[0] = sampleValPubkey1; - blsPubkeys[1] = sampleValPubkey2; - uint256[] memory slashIndexes = new uint256[](2); - slashIndexes[0] = 3; - slashIndexes[1] = 4; - - vm.prank(slashOracle); - uint64 instantSlasherType = 0; - vm.expectRevert( - abi.encodeWithSelector(IMevCommitMiddleware.OnlyVetoSlashersRequireExecution.selector, address(vault1), instantSlasherType) - ); - mevCommitMiddleware.executeSlashes(blsPubkeys, slashIndexes); - - blsPubkeys[0] = sampleValPubkey4; - blsPubkeys[1] = sampleValPubkey5; - slashIndexes[0] = 0; - slashIndexes[1] = 1; - - vm.expectRevert( - abi.encodeWithSelector(IMevCommitMiddleware.OnlySlashOracle.selector, slashOracle) - ); - vm.prank(vm.addr(0x1119)); - mevCommitMiddleware.executeSlashes(blsPubkeys, slashIndexes); - - vm.expectEmit(true, true, true, true); - emit ExecuteSlash(0, 20); - vm.expectEmit(true, true, true, true); - emit ExecuteSlash(1, 20); - vm.prank(slashOracle); - mevCommitMiddleware.executeSlashes(blsPubkeys, slashIndexes); - - allocatedStake = delegator.stake(bytes32("subnet"), operator1); - assertEq(allocatedStake, 59); - - assertEq(mevCommitMiddleware.getNumSlashableVals(address(vault2), operator1), 2); + assertEq(mevCommitMiddleware.getNumSlashableVals(address(vault2), operator1), 2); // TODO: revisit assertEq(mevCommitMiddleware.valsetLength(address(vault2), operator1), 3); assertEq(mevCommitMiddleware.getPositionInValset(sampleValPubkey6, address(vault2), operator1), 1); @@ -1134,7 +1102,7 @@ contract MevCommitMiddlewareTestCont is MevCommitMiddlewareTest { timestamps[0] = 203; vm.prank(slashOracle); vm.expectEmit(true, true, true, true); - emit ValidatorSlashRequested(sampleValPubkey6, operator1, address(vault2), 2); + emit ValidatorSlashed(sampleValPubkey6, operator1, address(vault2), slashAmount); vm.expectEmit(true, true, true, true); expectedVaults = new address[](1); expectedVaults[0] = address(vault2);