Skip to content

Commit

Permalink
feat: streamlined symbiotic veto slasher handling (#466)
Browse files Browse the repository at this point in the history
  • Loading branch information
shaspitz authored Nov 5, 2024
1 parent 30e7da5 commit ff04f83
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 125 deletions.
57 changes: 17 additions & 40 deletions contracts-abi/abi/MevCommitMiddleware.abi
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,6 @@
"type": "receive",
"stateMutability": "payable"
},
{
"type": "function",
"name": "EXECUTE_SLASH_PHASE_DURATION_SECONDS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "UPGRADE_INTERFACE_VERSION",
Expand Down Expand Up @@ -1564,7 +1551,7 @@
},
{
"type": "error",
"name": "InvalidVaultEpochDurationConsideringSlashPeriod",
"name": "InvalidVaultEpochDuration",
"inputs": [
{
"name": "vault",
Expand All @@ -1583,32 +1570,6 @@
}
]
},
{
"type": "error",
"name": "InvalidVaultEpochDurationForVetoSlasher",
"inputs": [
{
"name": "vault",
"type": "address",
"internalType": "address"
},
{
"name": "vaultEpochDurationSec",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "vetoDurationSec",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "executeSlashPhaseDurationSec",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"type": "error",
"name": "MissingOperatorRecord",
Expand Down Expand Up @@ -2153,6 +2114,22 @@
}
]
},
{
"type": "error",
"name": "VetoDurationTooShort",
"inputs": [
{
"name": "vault",
"type": "address",
"internalType": "address"
},
{
"name": "vetoDuration",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"type": "error",
"name": "VetoSlasherMustHaveZeroResolver",
Expand Down
33 changes: 1 addition & 32 deletions contracts-abi/clients/MevCommitMiddleware/MevCommitMiddleware.go

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions contracts/contracts/interfaces/IMevCommitMiddleware.sol
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,7 @@ interface IMevCommitMiddleware {

error SlashAmountMustBeNonZero(address vault);

error InvalidVaultEpochDurationConsideringSlashPeriod(address vault, uint256 vaultEpochDurationSec, uint256 slashPeriodSec);

error InvalidVaultEpochDurationForVetoSlasher(address vault, uint256 vaultEpochDurationSec,
uint256 vetoDurationSec, uint256 executeSlashPhaseDurationSec);
error InvalidVaultEpochDuration(address vault, uint256 vaultEpochDurationSec, uint256 slashPeriodSec);

error FullRestakeDelegatorNotSupported(address vault);

Expand All @@ -176,6 +173,8 @@ interface IMevCommitMiddleware {

error VetoSlasherMustHaveZeroResolver(address vault);

error VetoDurationTooShort(address vault, uint256 vetoDuration);

error UnknownSlasherType(address vault, uint256 slasherType);

error OnlyVetoSlashersRequireExecution(address vault, uint256 slasherType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,23 +473,19 @@ contract MevCommitMiddleware is IMevCommitMiddleware, MevCommitMiddlewareStorage
uint256 slasherType = IEntity(slasher).TYPE();
if (slasherType == _VETO_SLASHER_TYPE) {
IVetoSlasher vetoSlasher = IVetoSlasher(slasher);
// Explicit check preventing underflow revert.
require(vaultEpochDurationSeconds >= vetoSlasher.vetoDuration() + EXECUTE_SLASH_PHASE_DURATION_SECONDS,
InvalidVaultEpochDurationForVetoSlasher(vault, vaultEpochDurationSeconds,
vetoSlasher.vetoDuration(), EXECUTE_SLASH_PHASE_DURATION_SECONDS));
// For veto slashers, incorporate that veto duration will eat into vault's epoch duration.
vaultEpochDurationSeconds -= vetoSlasher.vetoDuration();
// Also incorporate that the oracle would need EXECUTE_SLASH_PHASE_DURATION_SECONDS to call executeSlashes,
// and this will eat into the vault's epoch duration.
vaultEpochDurationSeconds -= EXECUTE_SLASH_PHASE_DURATION_SECONDS;
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));
} else if (slasherType != _INSTANT_SLASHER_TYPE) {
revert UnknownSlasherType(vault, slasherType);
}

require(vaultEpochDurationSeconds > slashPeriodSeconds,
InvalidVaultEpochDurationConsideringSlashPeriod(vault, vaultEpochDurationSeconds, slashPeriodSeconds));
InvalidVaultEpochDuration(vault, vaultEpochDurationSeconds, slashPeriodSeconds));

_setVaultRecord(vault, slashAmount);
emit VaultRegistered(vault, slashAmount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ abstract contract MevCommitMiddlewareStorage {
/// @notice Enum TYPE for Symbiotic core VetoSlasher.
uint64 internal constant _VETO_SLASHER_TYPE = 1;

/// @notice Static duration of time for the oracle to call executeSlashes during the execute phase of veto slashing.
/// @dev See https://docs.symbiotic.fi/core-modules/vaults/#veto-slashing for more details.
uint256 public constant EXECUTE_SLASH_PHASE_DURATION_SECONDS = 60 minutes; // compiles to seconds
/// @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;
Expand Down
20 changes: 15 additions & 5 deletions contracts/contracts/validator-registry/middleware/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,25 @@ For validators who proposed incorrectly as determined by the oracle, slashing mu

### Instant vs Veto slashers

Vaults with instant slashers must have an `epochDuration` greater than than `slashPeriodSeconds` to register with our middleware contract, ensuring collateral is slashable during the full slashing period. Vaults with veto slashers must have an `epochDuration` greater than `slashPeriodSeconds` + `vetoDuration` + `executeSlashPhaseDuration`, where `vetoDuration` is specified by the slasher. `executeSlashPhaseDuration` is a constant value of 60 minutes for the `MevCommitMiddleware` contract.
Read more about Symbiotic slashing [here](https://docs.symbiotic.fi/core-modules/vaults#slashing).

Read more about Symbiotic slashing guarantees [here](https://docs.symbiotic.fi/core-modules/vaults#slashing).
Vaults with instant slashers must have an `epochDuration` greater than `slashPeriodSeconds` to register with our middleware contract, ensuring collateral is slashable during the full slashing period.

Since a permissioned oracle account invokes slashing, the mev-commit middleware contract only requires the most basic slashing interface. Hence for Vaults that use a `VetoSlasher`, the resolver is required to be disabled via `address(0)`.
Vaults with veto slashers:

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`. If the slasher type is `VETO_SLASHER_TYPE`, the oracle is responsible for calling `MevCommitMiddleware.executeSlashes` during the execute phase, 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.
* 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.

No action is required from the oracle during the veto phase, and following the veto phase, the oracle has a static 60 minute window, during which `executeSlashes` must be called. Read more about Symbiotic veto slashing [here](https://docs.symbiotic.fi/core-modules/vaults#veto-slashing).
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`.

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`.

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:

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).

### Configuration of slashPeriodSeconds

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ contract MevCommitMiddlewareTest is Test {
vaultFactoryMock = new RegistryMock();

network = vm.addr(0x1);
slashPeriodSeconds = 150;
slashPeriodSeconds = 150 hours;
slashOracle = vm.addr(0x2);
owner = vm.addr(0x3);

Expand All @@ -97,8 +97,9 @@ contract MevCommitMiddlewareTest is Test {

mockDelegator1 = new MockDelegator(15);
mockDelegator2 = new MockDelegator(16);
vault1 = new MockVault(address(mockDelegator1), address(0), 10);
vault2 = new MockVault(address(mockDelegator2), address(0), 10);
uint48 epochDuration = 10 hours;
vault1 = new MockVault(address(mockDelegator1), address(0), epochDuration);
vault2 = new MockVault(address(mockDelegator2), address(0), epochDuration);
}

function test_setters() public {
Expand Down Expand Up @@ -593,7 +594,7 @@ contract MevCommitMiddlewareTest is Test {
);
mevCommitMiddleware.registerVaults(vaults, slashAmounts);

uint256 vetoDuration = 5;
uint256 vetoDuration = 5 hours;
MockVetoSlasher mockSlasher1 = new MockVetoSlasher(77, address(77), vetoDuration, mockDelegator1, address(mevCommitMiddleware));
MockInstantSlasher mockSlasher2 = new MockInstantSlasher(88, mockDelegator2);

Expand All @@ -610,38 +611,35 @@ contract MevCommitMiddlewareTest is Test {

mockSlasher1.setType(vetoSlasherType);

assertEq(10, vault1.epochDuration());
assertEq(10, vault2.epochDuration());

uint256 executeSlashPhaseDuration = 60 minutes; // Constant from MevCommitMiddlewareStorage.

vm.prank(owner);
vm.expectRevert(
abi.encodeWithSelector(IMevCommitMiddleware.InvalidVaultEpochDurationForVetoSlasher.selector, vault1,
10, vetoDuration, executeSlashPhaseDuration)
abi.encodeWithSelector(IMevCommitMiddleware.VetoSlasherMustHaveZeroResolver.selector, vault1)
);
mevCommitMiddleware.registerVaults(vaults, slashAmounts);

MockVault(vault1).setEpochDuration(uint48(vetoDuration + executeSlashPhaseDuration + 1));
MockVault(vault2).setEpochDuration(uint48(vetoDuration + executeSlashPhaseDuration + 1));
mockSlasher1.setResolver(address(0));

assertEq(10 hours, vault1.epochDuration());

vm.prank(owner);
vm.expectRevert(
abi.encodeWithSelector(IMevCommitMiddleware.VetoSlasherMustHaveZeroResolver.selector, vault1)
abi.encodeWithSelector(IMevCommitMiddleware.InvalidVaultEpochDuration.selector, vault1,
10 hours - vetoDuration, 150 hours)
);
mevCommitMiddleware.registerVaults(vaults, slashAmounts);

mockSlasher1.setResolver(address(0));

MockVault(vault1).setEpochDuration(151 hours);
MockVault(vault2).setEpochDuration(151 hours);

vm.prank(owner);
vm.expectRevert(
abi.encodeWithSelector(IMevCommitMiddleware.InvalidVaultEpochDurationConsideringSlashPeriod.selector, vault1,
1, 150)
abi.encodeWithSelector(IMevCommitMiddleware.InvalidVaultEpochDuration.selector, vault1,
146 hours, 150 hours)
);
mevCommitMiddleware.registerVaults(vaults, slashAmounts);

MockVault(vault1).setEpochDuration(uint48(vetoDuration + executeSlashPhaseDuration + slashPeriodSeconds + 1));
MockVault(vault2).setEpochDuration(uint48(vetoDuration + executeSlashPhaseDuration + slashPeriodSeconds + 1));
MockVault(vault2).setEpochDuration(157 hours);
MockVault(vault1).setEpochDuration(157 hours);

vm.prank(owner);
vm.expectRevert(
Expand Down Expand Up @@ -800,7 +798,7 @@ contract MevCommitMiddlewareTest is Test {
vm.warp(888);

test_registerVaults();

IMevCommitMiddleware.VaultRecord memory vaultRecord1 = getVaultRecord(address(vault1));
IMevCommitMiddleware.VaultRecord memory vaultRecord2 = getVaultRecord(address(vault2));
assertTrue(vaultRecord1.exists);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,14 @@ contract MevCommitMiddlewareTestCont is MevCommitMiddlewareTest {
uint64 vetoSlasherType = 1;

MockInstantSlasher mockSlasher1 = new MockInstantSlasher(instantSlasherType, mockDelegator1);
MockVetoSlasher mockSlasher2 = new MockVetoSlasher(vetoSlasherType, address(0), 5, mockDelegator2, address(mevCommitMiddleware));
uint256 vetoDuration = 5 hours;
MockVetoSlasher mockSlasher2 = new MockVetoSlasher(vetoSlasherType, address(0), vetoDuration, mockDelegator2, address(mevCommitMiddleware));

vault1.setSlasher(address(mockSlasher1));
vault2.setSlasher(address(mockSlasher2));

vault1.setEpochDuration(151);
vault2.setEpochDuration(151 + 5 + 60 minutes);
vault1.setEpochDuration(151 hours);
vault2.setEpochDuration(151 hours + 5 hours);

vm.prank(address(vault1));
vaultFactoryMock.register();
Expand Down Expand Up @@ -127,14 +128,14 @@ contract MevCommitMiddlewareTestCont is MevCommitMiddlewareTest {
uint64 vetoSlasherType = 1;

MockInstantSlasher mockSlasher1 = new MockInstantSlasher(instantSlasherType, mockDelegator1);
uint256 vetoDuration = 5;
uint256 vetoDuration = 5 hours;
MockVetoSlasher mockSlasher2 = new MockVetoSlasher(vetoSlasherType, address(0), vetoDuration, mockDelegator2, address(mevCommitMiddleware));

vault1.setSlasher(address(mockSlasher1));
vault2.setSlasher(address(mockSlasher2));

vault1.setEpochDuration(151);
vault2.setEpochDuration(uint48(150 + 5 + 60 minutes + 1)); // slashPeriodSeconds + vetoDuration + executeSlashPhaseDuration + 1
vault1.setEpochDuration(151 hours);
vault2.setEpochDuration(151 hours + 5 hours);

vm.prank(owner);
mevCommitMiddleware.registerVaults(vaults, slashAmounts);
Expand Down Expand Up @@ -798,7 +799,7 @@ contract MevCommitMiddlewareTestCont is MevCommitMiddlewareTest {
emit ValidatorSlashed(sampleValPubkey1, operator1, address(vault1), 10);
mevCommitMiddleware.slashValidators(blsPubkeys, timestamps); // slash successful with req dereg

vm.warp(block.timestamp + 1000);
vm.warp(block.timestamp + slashPeriodSeconds + 1);
vm.prank(owner);
mevCommitMiddleware.deregisterValidators(blsPubkeys);

Expand Down Expand Up @@ -835,7 +836,7 @@ contract MevCommitMiddlewareTestCont is MevCommitMiddlewareTest {
emit ValidatorSlashed(sampleValPubkey1, operator1, address(vault1), 10);
mevCommitMiddleware.slashValidators(blsPubkeys, timestamps); // slash successful with req dereg

vm.warp(block.timestamp + 1000);
vm.warp(block.timestamp + slashPeriodSeconds + 1);
vm.prank(owner);
mevCommitMiddleware.deregisterVaults(vaults);

Expand Down Expand Up @@ -870,7 +871,7 @@ contract MevCommitMiddlewareTestCont is MevCommitMiddlewareTest {
emit ValidatorSlashed(sampleValPubkey1, operators[0], address(vault1), 10);
mevCommitMiddleware.slashValidators(blsPubkeys, timestamps); // slash successful with req dereg

vm.warp(block.timestamp + 1000);
vm.warp(block.timestamp + slashPeriodSeconds + 1);
vm.prank(owner);
mevCommitMiddleware.deregisterOperators(operators);

Expand Down Expand Up @@ -1185,7 +1186,7 @@ contract MevCommitMiddlewareTestCont is MevCommitMiddlewareTest {
vm.prank(owner);
mevCommitMiddleware.requestValDeregistrations(allPubkeys);

vm.warp(block.timestamp + 1000);
vm.warp(block.timestamp + slashPeriodSeconds + 1);

vm.prank(owner);
mevCommitMiddleware.deregisterValidators(allPubkeys);
Expand Down

0 comments on commit ff04f83

Please sign in to comment.