Skip to main content

Handbook for Networks

Context

In this document, we describe how networks can integrate with Symbiotic, the steps involved, and provide a set of questions for networks willing to integrate with Symbiotic to identify what sort of technical support is needed from Symbiotic core contributors.

Integration Guide

🌟 Introduction

Symbiotic is a shared security protocol that serves as a thin coordination layer, empowering network builders to control and adapt their own (re)staking implementation in a permissionless manner. It provides flexible (re)staking infrastructure for networks, operators, and stakers.

Our ecosystem allows anyone to build their ideal staking/restaking model using:

  • Ready-to-go open-source components
  • Custom modules compatible with Symbiotic Core
note

Symbiotic Core standardizes crucial elements of (re)staking such as stake accounting, slashing, and delegations.

This guide will walk you through how a network operates within the Symbiotic ecosystem and the requirements for integration.

🧩 Network Abstraction

Network abstract representation

Before we dig into technicalities, let’s define the network model and its key parts. The way we define this model is maximally abstract to support any kind of network.

In general, the network can be represented as:

  • VALSET is a part of network middleware deployed on the Ethereum network that provides information about validators (keys and stakes) working in the network. For example, it can filter them by minimal stake or implement a schedule for the validators.
  • SLASH VERIFIER is a part of network middleware deployed on the Ethereum network. It receives slashing requests, validates the sender and the requests themselves, and then interacts with vaults to request the slashing of the operator.
  • VERIFIER is a mechanism for validating the results of the operator's work. For example, in an oracle network, the verifier can be a contract that checks the validity of aggregated signatures from oracle nodes. The verifier can be located on Ethereum as a smart contract, removing trust assumptions on the delivery of information from the VERIFIER to SLASH VERIFIER. The VERIFIER can also be on another chain, which introduces some trust assumptions for such delivery.
  • SLASHER is a mechanism of slashing in the network. It consists of rules and methods to check for participant misbehavior. It also includes how slashing requests are delivered to the SLASH VERIFIER. For example, it can be a module of the PBFT protocol or an ordinary EOA if the network uses fraud proofs for slashing.
  • Input task storage is a place where tasks are stored. It can be a mempool with transactions, a DA layer, a smart contract on Ethereum with requests, and so on, depending on the network specification.
  • INPUT TASK : The required input data for computation. It might be empty if the computation doesn’t require any input.
  • OUTPUT: The result of the computation.
  • WORKER: operators, groups of operators, or a separate operator. For example, in the provers market network, a WORKER can be a separate operator. However, in a network with some consensus engine a WORKER is likely a majority of validators from VALSET.

With the following functionality:

  • COMPUTE(VALSET, INPUT TASK) -> OUTPUT: This function represents the main business logic of the network, such as price updates in the case of an oracle, or state transition in the case of a rollup.
  • VERIFY(VALSET, INPUT TASK, OUTPUT) -> Boolean: This function is responsible for verifying the correctness of the OUTPUT against VALSET and INPUT.
  • SLASH(VALIDATOR, AMOUNT): This function is responsible for slashing the validator by AMOUNT.

We assume that all of this components except COMPUTE are sitting on-chain in NetworkMiddleware contract.

In this abstraction Symbiotic key goal is to provide convenient way to maintain VALSET by:

  • Symbiotic core contracts are providing stakes and ability to slash this stake with strong guarantees over time
  • Symbiotic SDK contracts are providing the set of open-source components to build:
    • Operators election and stakes aggregation from many vaults
    • Operators key management
    • Slashing distribution across many vaults
    • Rewards distribution to operators and vaults

As we defined in the previous section, a network needs to implement the NetworkMiddleware contract to interact with Symbiotic core contracts.

We don’t require networks to use a specific implementation of the NetworkMiddleware. Instead, we define the Core API and provide fully open-source SDK modules and examples to simplify the integration process.

The NetworkMiddleware consists of two main parts: VALSETand SLASH VERIFIER.

Network flow

[task] → [input task storage] → [worker] → [verifier]

The network receives tasks from users and stores them in what we call input task storage. We do not specify exactly how tasks are stored; it can range from a complicated DA layer to a trivial immediate execution that does not require storing tasks (although the trivial storage is still considered storage).

Then the tasks from the storage are delivered to the WORKER(we can simplify it here and call workers "operators"). In practically every network, the results of the operator’s work should be checked somehow. So, the results of the operator's work are delivered to the VERIFIER. Even in fraud-proof systems, we can outline what the VERIFIER might look like. To perform a fraud-proof check, it must be verifiable onchain. However, the way this data is delivered to the chain, where the fraud proof is checked, is itself a result delivery from the WORKER to the VERIFIER.

[valset] → ([worker], [verifier])

In general, in every network, information about VALSET must be delivered to the WORKER. Operators should understand when they are eligible to work in the Network.

In some networks, it is essential to deliver VALSET to the VERIFIER as well. The VERIFIER can use this information to validate the operator’s work, such as eligibility and voting powers.

If core components of the network are not located on Ethereum, VALSET delivery can be made verifiable when a party signs a message with the actual VALSET. This message can then be used in fraud proofs for on-chain verification. If core components of the network, like WORKER and VERIFIER, are located on Ethereum, there are no trust assumptions on such delivery.

[slasher] → [slash verifier] → [request slash]

As mentioned, SLASHER is a mechanism for slashing in the network. Using information from modules like VERIFIER and WORKER, it determines when the operator must be slashed. However, the slashing is executed on the Ethereum chain, so the slashing information must be delivered to Ethereum. SLASH VERIFIER checks the sender of the slashing request, validates the calldata, and if all conditions are met, initiates the slashing process. Sometimes it requires data from the network’s VERIFIER, which can be on another chain. Slashing data can be delivered by a trusted party, aggregated key, directly in the chain, natively to the chain, etc. So the main question here is how exactly it is delivered to and verified in SLASH VERIFIER.

Validator Set

The validator set is an array of operators with their properties:

struct OperatorData {
opID, // global operator ID in symbiotic
votingPower, // voting power depends on stake
key, // operator key
}

VALSET = OperatorData[]
note

Symbiotic provides all data related to operators and networks in a historical manner, allowing for deterministic reconstruction of OperatorData at any time.

Submission Verification

When operators submit results onchain, the contract should verify this submission against the actual VALSET:

ACTUAL_VAL_SET_TIME // time when actual VAL_SET was formed

function verify(submission) {
VAL_SET = getValSetAt(ACTUAL_VAL_SET_TIME) // VAL_SET reconstruction

// verify submission against VAL_SET
// submission can include signatures or other authorization primitive
}

Slashing Logic

NET_ID = 1 // global network ID in Symbiotic
ACTUAL_VAL_SET_TIME; // time when actual VAL_SET was formed

function slash(operator, vault, amount) {
opStake = getOpStakeAt(operator, vault) // get op stake at

// cannot slash this stake if it was taken more than epochDuration ago
if now() - vault.epochDuration() > ACTUAL_VAL_SET_TIME:
return

// cannot slash more than given stake
if amount > opStake:
return

vault.slash(NET_ID, operator, amount, ACTUAL_VAL_SET_TIME)
}

🕰️ VALSET Tracking and Network Epoch

Network operation diagram

Network operation diagram

As we learned in previous sections, VALSET can be reconstructed at any time in the past.

This approach allows to deterministically synchronize the VALSET from Ethereum state by:

  • Track Ethereum finalized block using light-client or other mechanic depending on trust model
  • Extract VALSET from fetched Ethereum state using Execution Node
  • Do it in conveyor manner to guarantee enough time to handle late-coming slashing incidents (more details here)

A NETWORK_EPOCH is a period during which a certain operator set, obtained from the captured stake, operates for the network's benefit. Note that it’s not required to network’s epoch be same as the vault’s epoch. Instead the network’s epoch plus the vault's veto phase duration should not exceed the duration of the vault's epoch to ensure that withdrawals do not impact the captured stake and after veto phase there is time to actually slash the operator:

NETWORK_EPOCH + VETO_DURATION <= VAULT_EPOCH

This design ensures that the network has a stable set of operators for each epoch while allowing for periodic updates and maintain the integrity of the staking and slashing mechanisms.

💰 Staking

The vault allocates stakes by setting limits for networks and operators. Given the current active balance of the vault and the limits, we can capture the stake of the operator for the subsequent network epoch:

networkOperatorStake = vault.stakeAt(network, operator, timestamp, hints)

Staking Lifecycle:

  1. The network registers by calling NetworkRegistry.registerNetwork()
  2. Operators must opt into the vault and the network
  3. Stakers deposit funds into the vault
  4. The network sets a maximum stake amount for the vault by calling Vault.setMaxNetworkLimit(id, amount)
  5. The NETWORK_LIMIT_SET_ROLE holder defines the stake limit for the networks in the vault
  6. The OPERATOR_NETWORK_LIMIT_SET_ROLE holder defines the stake limit for the operators in the vault
warning

The current stake amount cannot be withdrawn for at least one epoch, although this restriction does not apply to cross-slashing.

👥 Operators

In Symbiotic, the operator can be either an EOA or a contract registered in the OperatorRegistry.

⚖️ Slashing

Each slashing incident consists of 3 separate actions:

  1. Request slash - requestSlash()
  2. Veto slash - vetoSlash() (optional)
  3. Execute slash - executeSlash()

The network's middleware calls the requestSlash() method with a given operatorresolver, and amount. In return, it receives the requestIndex of the slashing. The slashing is not applied instantly.

  • The slashing can be vetoed during the veto phase by the resolver, and such a slashing will not be executed.
  • If the veto phase is passed and the slashing is not vetoed, it can be executed via the executeSlash() method. The network's middleware can call this method after the veto phase has passed.
  • Important to note that each slashing has an executeDeadline. If the slashing was not executed before executeDeadline it can't be executed anymore.

Each slashing reduces the limits of the slashed operator and the network requested to slash. After the slashing all the user's funds are decreased proportionally.

🏆 Rewards

Rewards in Symbiotic are categorized into:

  1. Operator rewards
  2. Staker rewards

Operator Rewards

Networks have flexibility in distributing operator rewards. Common approaches include:

  • Off-chain calculations with batch transfers
  • Off-chain calculations with Merkle tree for claiming
  • On-chain reward calculations within the network middleware

Staker Rewards

Staker rewards are typically handled through the vault's reward distribution mechanism. The IRewardsDistributor interface provides a standardized way to distribute rewards across networks.

🛡️ Resolvers

Symbiotic introduces resolvers to handle slashing incidents flexibly. Resolvers are contracts or entities that can veto slashing incidents forwarded from networks.

Key Points:

  • Resolvers can be shared across networks
  • Determined through terms proposed by networks and accepted by vaults
  • A vault can allow multiple resolvers with different allocations
  • Can include decentralized dispute resolution frameworks (e.g., UMA, Kleros, reality.eth)
  • Possibility to require a quorum of resolvers for additional security

Veto Process

  1. Resolver listens for slashing requests in the vault
  2. When a relevant request is found, the resolver has a set time to veto or agree with the slashing
  3. If a resolver doesn't veto, the request is considered approved for slashing. A resolver can also partially slash.
  4. Each slashing request has its own veto deadline defined by the vault
note

Integrating with resolvers can provide additional security and flexibility in handling slashing incidents for your network.

🏗️ Contracts Architecture

Understanding the contracts architecture is crucial for effective integration. Here's an overview of key components:

Upgradability:

  • Immutable: Cannot be upgraded
  • Migratable: Can be upgraded by owners to newer versions provided by its factory
  • Not Specified: May or may not be upgradeable

Accessibility:

  • VaultFactory, DelegatorFactory, and SlasherFactory: Ownable, their owners can whitelist new versions/types
  • Vault: Ownable, its owner can migrate it to newer versions
  • Vault, Delegator, and Slasher: Have an AccessControl (roles mechanic) for providing granularity across accessibility risks
  • Collateral, StakerRewards, and OperatorRewards: Can have any accessibility

Integration Considerations:

  1. When interacting with Vaults, be aware of potential upgrades and role-based access control
  2. Ensure your network's middleware is compatible with the latest Vault implementations
  3. Consider the immutability of core contracts when designing your network's architecture

🔗 Network Integration Steps

  1. Implement the NetworkMiddleware contract, including the required slashing logic
  2. Register the network using NetworkRegistry.registerNetwork()
  3. Set maximum stake amounts for each vault with Vault.setMaxNetworkLimit(resolver, amount)
  4. Implement operator selection logic within your middleware or network contract
  5. Set up reward distribution mechanisms for both operators and stakers
  6. Regularly update your operator set based on current stakes and opt-in statuses
  7. Implement resolver integration if desired, including listening for and responding to slashing requests
  8. Consider the contracts' upgradability and accessibility when designing your network's long-term strategy