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 in router #4403

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vm.computeCreateAddress(deployerAddress, deployerNonce) is used to compute the future contract address. after mirrorProxy = new MirrorProxy(address(router)) you need to create middleware to make this address valid.


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);
Comment on lines +73 to +79

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indexed is not needed, because the observer is watching many events at once

Suggested change
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);
event RequestSlashCommitmentProcessed(bytes32 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 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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is not possible to sent a slash for the previous era? for example we found that validator from the previous era did something wrong - why we cannot to sent a slash for the funds it had in the previous era?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah now I can see. I think era param should be removed from commitment. Cannot see why do we need it. Slashes have already all informations about timestamps.


// 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");
Comment on lines +343 to +344
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mathematically impossible case


// 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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not important: Looks like over-checking. This must be checked in constructor


IMiddleware middlewareInstance = IMiddleware(middlewareAddress);

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

emit RequestSlashCommitmentProcessed(commitmentHash, commitment.slashes);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think is enough to sent a commitmentHash. This is cheaper. And validators could save their slashes in db by hash and access them if they need in future.

}

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