Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ethexe): Support slash commitments for validators #4410

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions ethexe/common/src/gear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,37 @@ pub struct ValidatorsCommitment {
pub era_index: u64,
}

#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
pub struct RequestSlashCommitment {
pub era_index: u64,
pub slashes: Vec<SlashData>,
}

#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
pub struct ExecuteSlashCommitment {
pub era_index: u64,
pub slash_identifiers: Vec<SlashIdentifier>,
}

#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
pub struct SlashData {
pub operator: ActorId,
pub timestamp: u64,
pub vaults: Vec<VaultSlashData>,
}

#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
pub struct VaultSlashData {
pub vault: ActorId,
pub amount: U256,
}

#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
pub struct SlashIdentifier {
pub vault: ActorId,
pub index: u64,
}

#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
pub enum CodeState {
#[default]
Expand Down
4 changes: 4 additions & 0 deletions ethexe/contracts/script/Deployment.s.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import {Middleware} from "../src/Middleware.sol";
import {Mirror} from "../src/Mirror.sol";
import {MirrorProxy} from "../src/MirrorProxy.sol";
import {Router} from "../src/Router.sol";
Expand All @@ -16,6 +17,7 @@ contract DeploymentScript is Script {
Router public router;
Mirror public mirror;
MirrorProxy public mirrorProxy;
Middleware public middleware;

function setUp() public {}

Expand All @@ -34,6 +36,7 @@ contract DeploymentScript is Script {

address mirrorAddress = vm.computeCreateAddress(deployerAddress, vm.getNonce(deployerAddress) + 2);
address mirrorProxyAddress = vm.computeCreateAddress(deployerAddress, vm.getNonce(deployerAddress) + 3);
address middlewareAddress = vm.computeCreateAddress(deployerAddress, vm.getNonce(deployerAddress) + 4);

router = Router(
Upgrades.deployTransparentProxy(
Expand All @@ -46,6 +49,7 @@ contract DeploymentScript is Script {
mirrorAddress,
mirrorProxyAddress,
address(wrappedVara),
middlewareAddress,
1 days,
2 hours,
validatorsArray
Expand Down
74 changes: 74 additions & 0 deletions ethexe/contracts/src/IMiddleware.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

interface IMiddleware {
struct VaultSlashData {
address vault;
uint256 amount;
}

struct SlashData {
address operator;
uint48 ts;
VaultSlashData[] vaults;
}

struct SlashIdentifier {
address vault;
uint256 index;
}

struct Config {
uint48 eraDuration;
uint48 minVaultEpochDuration;
uint48 operatorGracePeriod;
uint48 vaultGracePeriod;
uint48 minVetoDuration;
uint48 minSlashExecutionDelay;
uint256 maxResolverSetEpochsDelay;
address vaultRegistry;
uint64 allowedVaultImplVersion;
uint64 vetoSlasherImplType;
address operatorRegistry;
address networkRegistry;
address networkOptIn;
address middlewareService;
address collateral;
address roleSlashRequester;
address roleSlashExecutor;
address vetoResolver;
}

function changeSlashRequester(address newRole) external;

function changeSlashExecutor(address newRole) external;

function registerOperator() external;

function disableOperator() external;

function enableOperator() external;

function unregisterOperator(address operator) external;

function registerVault(address vault) external;

function disableVault(address vault) external;

function enableVault(address vault) external;

function unregisterVault(address vault) external;

function makeElectionAt(uint48 ts, uint256 maxValidators) external view returns (address[] memory);

function getOperatorStakeAt(address operator, uint48 ts) external view returns (uint256);

function getActiveOperatorsStakeAt(uint48 ts)
external
view
returns (address[] memory activeOperators, uint256[] memory stakes);

function requestSlash(SlashData[] calldata data) external;

function executeSlash(SlashIdentifier[] calldata slashes) external;
}
22 changes: 22 additions & 0 deletions ethexe/contracts/src/IRouter.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import {IMiddleware} from "./Middleware.sol";
import {Gear} from "./libraries/Gear.sol";

/// @title Gear.exe Router Interface
Expand Down Expand Up @@ -65,6 +66,18 @@ interface IRouter {
/// @param codeId The code ID of the WASM implementation of the created program.
event ProgramCreated(address actorId, bytes32 indexed codeId);

/**
* @dev Emitted when a slash request is processed by the middleware.
* @param slashData An array of SlashData structures containing details of the slash request.
*/
event RequestSlashCommitmentProcessed(bytes32 indexed commitmentHash, IMiddleware.SlashData[] slashData);

/**
* @dev Emitted when a slash request is executed by the middleware.
* @param slashId An array of SlashIdentifier structures containing details of the slash execution.
*/
event ExecuteSlashCommitmentProcessed(bytes32 indexed commitmentHash, IMiddleware.SlashIdentifier[] slashId);

/// @notice Emitted when the router's storage slot has been changed.
/// @dev This is both an *informational* and *requesting* event, signaling that an authority decided to wipe the router state, rendering all previously existing codes and programs ineligible. Validators need to wipe their databases immediately.
event StorageSlotChanged();
Expand All @@ -77,6 +90,7 @@ interface IRouter {
function mirrorImpl() external view returns (address);
function mirrorProxyImpl() external view returns (address);
function wrappedVara() external view returns (address);
function middleware() external view returns (address);

function areValidators(address[] calldata validators) external view returns (bool);
function isValidator(address validator) external view returns (bool);
Expand Down Expand Up @@ -115,4 +129,12 @@ interface IRouter {
/// @dev NextEraValidatorsCommitted Emitted on success.
function commitValidators(Gear.ValidatorsCommitment calldata validatorsCommitment, bytes[] calldata signatures)
external;
function commitRequestSlash(
Gear.RequestSlashCommitment calldata requestSlashCommitment,
bytes[] calldata signatures
) external;
function commitExecuteSlash(
Gear.ExecuteSlashCommitment calldata executeSlashCommitment,
bytes[] calldata signatures
) external;
}
40 changes: 2 additions & 38 deletions ethexe/contracts/src/Middleware.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {INetworkMiddlewareService} from "symbiotic-core/src/interfaces/service/I
import {IVetoSlasher} from "symbiotic-core/src/interfaces/slasher/IVetoSlasher.sol";
import {IMigratableEntity} from "symbiotic-core/src/interfaces/common/IMigratableEntity.sol";

import {IMiddleware} from "./IMiddleware.sol";
import {MapWithTimeData} from "./libraries/MapWithTimeData.sol";

// TODO (asap): document all functions and variables
Expand All @@ -25,7 +26,7 @@ import {MapWithTimeData} from "./libraries/MapWithTimeData.sol";
// TODO: implement forced operators removal
// TODO: implement forced vaults removal
// TODO: use hints for symbiotic calls
contract Middleware {
contract Middleware is IMiddleware {
using EnumerableMap for EnumerableMap.AddressToUintMap;
using MapWithTimeData for EnumerableMap.AddressToUintMap;
using Subnetwork for address;
Expand Down Expand Up @@ -54,43 +55,6 @@ contract Middleware {
error ResolverMismatch();
error ResolverSetDelayTooLong();

struct VaultSlashData {
address vault;
uint256 amount;
}

struct SlashData {
address operator;
uint48 ts;
VaultSlashData[] vaults;
}

struct SlashIdentifier {
address vault;
uint256 index;
}

struct Config {
uint48 eraDuration;
uint48 minVaultEpochDuration;
uint48 operatorGracePeriod;
uint48 vaultGracePeriod;
uint48 minVetoDuration;
uint48 minSlashExecutionDelay;
uint256 maxResolverSetEpochsDelay;
address vaultRegistry;
uint64 allowedVaultImplVersion;
uint64 vetoSlasherImplType;
address operatorRegistry;
address networkRegistry;
address networkOptIn;
address middlewareService;
address collateral;
address roleSlashRequester;
address roleSlashExecutor;
address vetoResolver;
}

uint96 public constant NETWORK_IDENTIFIER = 0;

uint48 public immutable eraDuration;
Expand Down
74 changes: 73 additions & 1 deletion ethexe/contracts/src/Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.26;

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {Gear} from "./libraries/Gear.sol";
import {IMiddleware} from "./IMiddleware.sol";
import {IMirror} from "./IMirror.sol";
import {IMirrorDecoder} from "./IMirrorDecoder.sol";
import {IRouter} from "./IRouter.sol";
Expand All @@ -26,6 +27,7 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
address _mirror,
address _mirrorProxy,
address _wrappedVara,
address _middleware,
uint256 _eraDuration,
uint256 _electionDuration,
address[] calldata _validators
Expand All @@ -41,7 +43,7 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
Storage storage router = _router();

router.genesisBlock = Gear.newGenesis();
router.implAddresses = Gear.AddressBook(_mirror, _mirrorProxy, _wrappedVara);
router.implAddresses = Gear.AddressBook(_mirror, _mirrorProxy, _wrappedVara, _middleware);
router.validationSettings.signingThresholdPercentage = Gear.SIGNING_THRESHOLD_PERCENTAGE;
router.computeSettings = Gear.defaultComputationSettings();
router.timelines = Gear.Timelines(_eraDuration, _electionDuration);
Expand Down Expand Up @@ -110,6 +112,10 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
return _router().implAddresses.wrappedVara;
}

function middleware() public view returns (address) {
return _router().implAddresses.middleware;
}

function areValidators(address[] calldata _validators) public view returns (bool) {
Gear.Validators storage _currentValidators = Gear.currentEraValidators(_router());

Expand Down Expand Up @@ -323,6 +329,72 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
);
}

function commitRequestSlash(Gear.RequestSlashCommitment calldata commitment, bytes[] calldata signatures)
external
{
Storage storage router = _router();

uint256 currentEraIndex = (block.timestamp - router.genesisBlock.timestamp) / router.timelines.era;

// Validate the era index
require(commitment.eraIndex == currentEraIndex, "commitment era index is invalid");

// Ensure the commitment window is valid
uint256 eraStart = router.genesisBlock.timestamp + router.timelines.era * currentEraIndex;
require(block.timestamp >= eraStart, "current era has not started yet");

// Validate signatures
bytes32 commitmentHash = Gear.requestSlashCommitmentHash(commitment);
require(
Gear.validateSignatures(router, keccak256(abi.encodePacked(commitmentHash)), signatures),
"request slash commitment signatures verification failed"
);

// Ensure middleware is set
address middlewareAddress = router.implAddresses.middleware;
require(middlewareAddress != address(0), "Middleware address not set");

IMiddleware middlewareInstance = IMiddleware(middlewareAddress);

// Call the middleware's `requestSlash` function
middlewareInstance.requestSlash(commitment.slashes);

emit RequestSlashCommitmentProcessed(commitmentHash, commitment.slashes);
}

function commitExecuteSlash(Gear.ExecuteSlashCommitment calldata commitment, bytes[] calldata signatures)
external
{
Storage storage router = _router();

uint256 currentEraIndex = (block.timestamp - router.genesisBlock.timestamp) / router.timelines.era;

// Validate the era index
require(commitment.eraIndex == currentEraIndex, "commitment era index is invalid");

// Ensure the commitment window is valid
uint256 eraStart = router.genesisBlock.timestamp + router.timelines.era * currentEraIndex;
require(block.timestamp >= eraStart, "current era has not started yet");

// Validate signatures
bytes32 commitmentHash = Gear.executeSlashCommitmentHash(commitment);
require(
Gear.validateSignatures(router, keccak256(abi.encodePacked(commitmentHash)), signatures),
"execute slash commitment signatures verification failed"
);

// Ensure middleware is set
address middlewareAddress = router.implAddresses.middleware;
require(middlewareAddress != address(0), "Middleware address not set");

IMiddleware middlewareInstance = IMiddleware(middlewareAddress);

// Call the middleware's `executeSlash` function
middlewareInstance.executeSlash(commitment.slashIdentifiers);

emit ExecuteSlashCommitmentProcessed(commitmentHash, commitment.slashIdentifiers);
}

/* Helper private functions */

function _createProgram(bytes32 _codeId, bytes32 _salt) private returns (address) {
Expand Down
Loading
Loading