Deploy a ready-to-go Vault
Symbiotic is a highly modular protocol that already has a lot of separate parts and will have much more in the future. Therefore, this guide describes possible configurations of the Vault deployments.
Introduction
Throughout the document, we’ll use Foundry
example scripts from Symbiotic GitHub repositories to deploy all the modules. Therefore, here are the installation steps:
-
Make sure to have an installed
Git
(Download Git) -
Make sure to have an installed
Foundry
(Download Foundry)
General-purpose deployment
The first step for the ready-to-go Vault deployment is to determine how it is going to be used:
- As a very specific configuration inside some complex system of contracts that needs to be designed and created
- As a very pure configuration with most of the parameters non-updatable that can be hardly manageable in practice
- As some common configuration that may cover most of the use cases
For our needs, we choose the 3-rd option, and the steps to get a production-ready configuration are the following:
-
Deploy a Burner Router which is a specific implementation of the Burner module (see Burner module details)
-
Deploy core modules such as Vault, Delegator, Slasher using the deployed Burner Router address (see core modules details)
-
Deploy staker rewards with the use of the Vault’s address got during the previous step (see rewards details)
1. Burner Router
Burner is a contract that receives slashed collateral tokens. Symbiotic does not specify the whole processing of the funds flow after the slashing. For example, there are some possible ways:
- Fully burn the funds (unwrap the underlying assets if needed)
- Redistribute to good operators
- Compensate the victims (in case of using the stake as a security deposit)
From our side, we provide:
- DefaultBurners - pure burner contracts for several ETH LSTs (see the list) that withdraw ETH from Beacon Chain and burn it
- BurnerRouter - a router contract that allows redirection of the slashed funds to different addresses depending on the slashing networks and the slashed operators
Our pure burners have one advantage and disadvantage simultaneously - they are fully immutable, removing any trust assumptions in that direction. However, in case of the underlying LST’s upgrade of the contracts, their flow may break. Therefore, in general, we suggest using BurnerRouter, which contains the receivers’ update functionality, in pair with a DefaultBurner as a global receiver.
Burner Router deployment
- Clone the burners repository by running the following command:
git clone --recurse-submodules https://github.com/symbioticfi/burners.git
- Navigate into the cloned repository folder:
cd burners
- Deploy a burner router contract using a simple script:
It is an example command, meaning you need to replace the given values with necessary ones.
In addition, you need to choose a preferred wallet option and adjust the command accordingly.
- Mainnet
- Holesky
- Sepolia
BURNER_ROUTER_FACTORY=0x0000000000000000000000000000000000000000 # address of the BurnerRouterFactory (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the router’s owner
COLLATERAL=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 # address of the collateral - wstETH (MUST be the same as for the Vault to connect)
DELAY=1814400 # duration of the receivers’ update delay (= 21 days)
GLOBAL_RECEIVER=0x0000000000000000000000000000000000000000 # address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
NETWORK_RECEIVERS=[] # array with elements like "\(network_address,receiver_address\)" meaning network-specific receivers
OPERATOR_NETWORK_RECEIVERS=[] # array with elements like "\(network_address,operator_address,receiver_address\)" meaning operator-network-specific receivers
forge script script/deploy/BurnerRouter.s.sol:BurnerRouterScript \
$BURNER_ROUTER_FACTORY \
$OWNER \
$COLLATERAL \
$DELAY \
$GLOBAL_RECEIVER \
$NETWORK_RECEIVERS \
$OPERATOR_NETWORK_RECEIVERS \
--sig "run(address,address,address,uint48,address,(address,address)[],(address,address,address)[])" \
--rpc-url=https://ethereum-rpc.publicnode.com \
--chain mainnet \
--broadcast
BURNER_ROUTER_FACTORY=0x32e2AfbdAffB1e675898ABA75868d92eE1E68f3b # address of the BurnerRouterFactory (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the router’s owner
COLLATERAL=0x8d09a4502Cc8Cf1547aD300E066060D043f6982D # address of the collateral - wstETH (MUST be the same as for the Vault to connect)
DELAY=1814400 # duration of the receivers’ update delay (= 21 days)
GLOBAL_RECEIVER=0x25133c2c49A343F8312bb6e896C1ea0Ad8CD0EBd # address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
NETWORK_RECEIVERS=[] # array with elements like "\(network_address,receiver_address\)" meaning network-specific receivers
OPERATOR_NETWORK_RECEIVERS=[] # array with elements like "\(network_address,operator_address,receiver_address\)" meaning operator-network-specific receivers
forge script script/deploy/BurnerRouter.s.sol:BurnerRouterScript \
$BURNER_ROUTER_FACTORY \
$OWNER \
$COLLATERAL \
$DELAY \
$GLOBAL_RECEIVER \
$NETWORK_RECEIVERS \
$OPERATOR_NETWORK_RECEIVERS \
--sig "run(address,address,address,uint48,address,(address,address)[],(address,address,address)[])" \
--rpc-url=https://ethereum-holesky-rpc.publicnode.com \
--chain holesky \
--broadcast
BURNER_ROUTER_FACTORY=0x32e2AfbdAffB1e675898ABA75868d92eE1E68f3b # address of the BurnerRouterFactory (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the router’s owner
COLLATERAL=0xB82381A3fBD3FaFA77B3a7bE693342618240067b # address of the collateral - wstETH (MUST be the same as for the Vault to connect)
DELAY=1814400 # duration of the receivers’ update delay (= 21 days)
GLOBAL_RECEIVER=0x58D347334A5E6bDE7279696abE59a11873294FA4 # address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
NETWORK_RECEIVERS=[] # array with elements like "\(network_address,receiver_address\)" meaning network-specific receivers
OPERATOR_NETWORK_RECEIVERS=[] # array with elements like "\(network_address,operator_address,receiver_address\)" meaning operator-network-specific receivers
forge script script/deploy/BurnerRouter.s.sol:BurnerRouterScript \
$BURNER_ROUTER_FACTORY \
$OWNER \
$COLLATERAL \
$DELAY \
$GLOBAL_RECEIVER \
$NETWORK_RECEIVERS \
$OPERATOR_NETWORK_RECEIVERS \
--sig "run(address,address,address,uint48,address,(address,address)[],(address,address,address)[])" \
--rpc-url=https://ethereum-sepolia-rpc.publicnode.com \
--chain sepolia \
--broadcast
Burner Router deployment - technical (Solidity)
The following instruction assumes you have an already initialized Foundry repository (read more here).
- Install the burners repository by running the following command:
forge install symbioticfi/burners
- Update (or create if not yet) a
remappings.txt
file inside your repository accordingly:
...
@symbioticfi/burners/=lib/burners/
- Create a burner router contract using a BurnerRouterFactory contract:
It is an example code snippet, meaning you need to replace the given values with necessary ones.
- Mainnet
- Holesky
- Sepolia
import {IBurnerRouterFactory} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouterFactory.sol";
import {IBurnerRouter} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouter.sol";
// ...
address BURNER_ROUTER_FACTORY = 0x0000000000000000000000000000000000000000; // address of the BurnerRouterFactory (see Deployments page)
// ...
address burnerRouter = IBurnerRouterFactory(BURNER_ROUTER_FACTORY).create(
IBurnerRouter.InitParams({
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the router’s owner
collateral: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, // address of the collateral - wstETH (MUST be the same as for the Vault to connect)
delay: 1814400, // duration of the receivers’ update delay (= 21 days)
globalReceiver: 0x0000000000000000000000000000000000000000, // address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
networkReceivers: new IBurnerRouter.NetworkReceiver[](0), // array with IBurnerRouter.NetworkReceiver elements meaning network-specific receivers
operatorNetworkReceivers: new IBurnerRouter.OperatorNetworkReceiver[](0) // array with IBurnerRouter.OperatorNetworkReceiver elements meaning network-specific receivers
})
);
// ...
import {IBurnerRouterFactory} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouterFactory.sol";
import {IBurnerRouter} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouter.sol";
// ...
address BURNER_ROUTER_FACTORY = 0x32e2AfbdAffB1e675898ABA75868d92eE1E68f3b; // address of the BurnerRouterFactory (see Deployments page)
// ...
address burnerRouter = IBurnerRouterFactory(BURNER_ROUTER_FACTORY).create(
IBurnerRouter.InitParams({
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the router’s owner
collateral: 0x8d09a4502Cc8Cf1547aD300E066060D043f6982D, // address of the collateral - wstETH (MUST be the same as for the Vault to connect)
delay: 1814400, // duration of the receivers’ update delay (= 21 days)
globalReceiver: 0x25133c2c49A343F8312bb6e896C1ea0Ad8CD0EBd, // address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
networkReceivers: new IBurnerRouter.NetworkReceiver[](0), // array with IBurnerRouter.NetworkReceiver elements meaning network-specific receivers
operatorNetworkReceivers: new IBurnerRouter.OperatorNetworkReceiver[](0) // array with IBurnerRouter.OperatorNetworkReceiver elements meaning network-specific receivers
})
);
// ...
import {IBurnerRouterFactory} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouterFactory.sol";
import {IBurnerRouter} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouter.sol";
// ...
address BURNER_ROUTER_FACTORY = 0x32e2AfbdAffB1e675898ABA75868d92eE1E68f3b; // address of the BurnerRouterFactory (see Deployments page)
// ...
address burnerRouter = IBurnerRouterFactory(BURNER_ROUTER_FACTORY).create(
IBurnerRouter.InitParams({
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the router’s owner
collateral: 0xB82381A3fBD3FaFA77B3a7bE693342618240067b, // address of the collateral - wstETH (MUST be the same as for the Vault to connect)
delay: 1814400, // duration of the receivers’ update delay (= 21 days)
globalReceiver: 0x58D347334A5E6bDE7279696abE59a11873294FA4, // address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
networkReceivers: new IBurnerRouter.NetworkReceiver[](0), // array with IBurnerRouter.NetworkReceiver elements meaning network-specific receivers
operatorNetworkReceivers: new IBurnerRouter.OperatorNetworkReceiver[](0) // array with IBurnerRouter.OperatorNetworkReceiver elements meaning network-specific receivers
})
);
// ...
2. Core modules
Vault
Vault is a contract that:
- storages the collateral
- performs accounting in the sense of deposits and withdrawals
- processes slashing events by transferring the collateral to the burner
- allows deposit whitelisting and deposit limiting
For now, there are two types of Vaults we provide:
- Vault (version 1) - is a standard version of the Vault that is responsible for all of the functions mentioned above
- Tokenized Vault (version 2) - is an extended version of the Vault that represents stake shares as ERC-20 tokens
In general, we don’t need the tokenization of the stake as LRTs provide it. Therefore, we will use the common Vault.
Delegator
Delegator is a contract that allows Vault curator to allocate the stake to networks and operators.
At the current moment, we have three types of it:
- NetworkRestakeDelegator (type 0) - it accounts for allocations in absolute numbers for networks and in shares for operator-network pairs, so it allows having a staking and a common restaking across the networks (depending on the delegated amounts)
- FullRestakeDelegator (type 1) - it accounts for allocations in absolute numbers for both: networks and operator-network pairs, so it allows everything that NetworkRestakeDelegator allows and restaking across the operators inside the networks as an addition (you can read more about it here)
- OperatorSpecificDelegator (type 2) - it is a simplified version of NetworkRestakeDelegator where only one specific operator has allocations
FullRestakeDelegator is able to cover all the delegation use cases. However, it is also able to create highly risky configurations that need proper handling. OperatorSpecificDelegator limits the possible use cases for LRTs with a non-single number of operators. Hence, let’s choose NetworkRestakeDelegator for our needs.
Slasher
Slasher is a contract that is responsible for the proper penalty execution, preserving networks’ rights to be able to slash the captured stake and preserving stakers’ rights not to be slashed more than deserved.
There are two types of Slasher:
- Slasher (type 0) - is a common Slasher that receives slashing requests and instantly executes them
- VetoSlasher (type 1) - it allows to veto received slashing requests using resolvers
For the VetoSlasher, networks may propose resolvers that can veto the slashing requests. It is also possible for the networks not to set a resolver that enables an instant slashing mechanic similar to Slasher’s. If the Vault curator is not ready to provide a stake without the resolver, the curator may simply not allocate any stake to such networks. Since the VetoSlasher can be seen as an extension of the Slasher, we’ll choose it.
Core modules deployment
- Clone the core contracts repository by running the following command:
git clone --recurse-submodules https://github.com/symbioticfi/core.git
- Navigate into the cloned repository folder:
cd core
- Deploy core modules contracts using a simple script:
It is an example command, meaning you need to replace the given values with necessary ones.
In addition, you need to choose a preferred wallet option and adjust the command accordingly.
- Mainnet
- Holesky
- Sepolia
VAULT_CONFIGURATOR=0x0000000000000000000000000000000000000000 # address of the VaultConfigurator (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the deploying contracts’ owner/curator
COLLATERAL=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 # address of the collateral - wstETH
BURNER=<BURNER_ROUTER> # address of the deployed burner router
EPOCH_DURATION=604800 # duration of the Vault epoch in seconds (= 7 days)
ENABLE_WHITELIST=false # if enable deposit whitelisting
DEPOSIT_LIMIT=0 # deposit limit (not enabled in case of 0)
DELEGATOR_TYPE=0 # Delegator’s type (= NetworkRestakeDelegator)
HOOK=0x0000000000000000000000000000000000000000 # address of the hook (if not zero, is called via `onSlash()` function on each slashing)
ENABLE_SLASHER=true # if enable Slasher module
SLASHER_TYPE=1 # Slasher’s type (= VetoSlasher)
VETO_DURATION=86400 # veto duration (= 1 day)
forge script script/deploy/Vault.s.sol:VaultScript \
$VAULT_CONFIGURATOR \
$OWNER \
$COLLATERAL \
$BURNER \
$EPOCH_DURATION \
$ENABLE_WHITELIST \
$DEPOSIT_LIMIT \
$DELEGATOR_TYPE \
$HOOK \
$ENABLE_SLASHER \
$SLASHER_TYPE \
$VETO_DURATION \
--sig "run(address,address,address,address,uint48,bool,uint256,uint64,address,bool,uint64,uint48)" \
--rpc-url=https://ethereum-rpc.publicnode.com \
--chain mainnet \
--broadcast
VAULT_CONFIGURATOR=0xD2191FE92987171691d552C219b8caEf186eb9cA # address of the VaultConfigurator (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the deploying contracts’ owner/curator
COLLATERAL=0x8d09a4502Cc8Cf1547aD300E066060D043f6982D # address of the collateral - wstETH
BURNER=<BURNER_ROUTER> # address of the deployed burner router
EPOCH_DURATION=604800 # duration of the Vault epoch in seconds (= 7 days)
ENABLE_WHITELIST=false # if enable deposit whitelisting
DEPOSIT_LIMIT=0 # deposit limit (not enabled in case of 0)
DELEGATOR_TYPE=0 # Delegator’s type (= NetworkRestakeDelegator)
HOOK=0x0000000000000000000000000000000000000000 # address of the hook (if not zero, is called via `onSlash()` function on each slashing)
ENABLE_SLASHER=true # if enable Slasher module
SLASHER_TYPE=1 # Slasher’s type (= VetoSlasher)
VETO_DURATION=86400 # veto duration (= 1 day)
forge script script/deploy/Vault.s.sol:VaultScript \
$VAULT_CONFIGURATOR \
$OWNER \
$COLLATERAL \
$BURNER \
$EPOCH_DURATION \
$ENABLE_WHITELIST \
$DEPOSIT_LIMIT \
$DELEGATOR_TYPE \
$HOOK \
$ENABLE_SLASHER \
$SLASHER_TYPE \
$VETO_DURATION \
--sig "run(address,address,address,address,uint48,bool,uint256,uint64,address,bool,uint64,uint48)" \
--rpc-url=https://ethereum-holesky-rpc.publicnode.com \
--chain holesky \
--broadcast
VAULT_CONFIGURATOR=0xD2191FE92987171691d552C219b8caEf186eb9cA # address of the VaultConfigurator (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the deploying contracts’ owner/curator
COLLATERAL=0xB82381A3fBD3FaFA77B3a7bE693342618240067b # address of the collateral - wstETH
BURNER=<BURNER_ROUTER> # address of the deployed burner router
EPOCH_DURATION=604800 # duration of the Vault epoch in seconds (= 7 days)
ENABLE_WHITELIST=false # if enable deposit whitelisting
DEPOSIT_LIMIT=0 # deposit limit (not enabled in case of 0)
DELEGATOR_TYPE=0 # Delegator’s type (= NetworkRestakeDelegator)
HOOK=0x0000000000000000000000000000000000000000 # address of the hook (if not zero, is called via `onSlash()` function on each slashing)
ENABLE_SLASHER=true # if enable Slasher module
SLASHER_TYPE=1 # Slasher’s type (= VetoSlasher)
VETO_DURATION=86400 # veto duration (= 1 day)
forge script script/deploy/Vault.s.sol:VaultScript \
$VAULT_CONFIGURATOR \
$OWNER \
$COLLATERAL \
$BURNER \
$EPOCH_DURATION \
$ENABLE_WHITELIST \
$DEPOSIT_LIMIT \
$DELEGATOR_TYPE \
$HOOK \
$ENABLE_SLASHER \
$SLASHER_TYPE \
$VETO_DURATION \
--sig "run(address,address,address,address,uint48,bool,uint256,uint64,address,bool,uint64,uint48)" \
--rpc-url=https://ethereum-sepolia-rpc.publicnode.com \
--chain sepolia \
--broadcast
Core modules deployment - technical (Solidity)
The following instruction assumes you have an already initialized Foundry repository (read more here).
- Install the core contracts repository by running the following command:
forge install symbioticfi/core
- Update (or create if not yet) a
remappings.txt
file inside your repository accordingly:
...
@symbioticfi/core/=lib/core/
- Create core modules using a VaultConfigurator contract:
It is an example code snippet, meaning you need to replace the given values with necessary ones.
- Mainnet
- Holesky
- Sepolia
import {IVaultConfigurator} from "@symbioticfi/core/src/interfaces/IVaultConfigurator.sol";
import {IVault} from "@symbioticfi/core/src/interfaces/vault/IVault.sol";
import {IBaseDelegator} from "@symbioticfi/core/src/interfaces/delegator/IBaseDelegator.sol";
import {INetworkRestakeDelegator} from "@symbioticfi/core/src/interfaces/delegator/INetworkRestakeDelegator.sol";
import {IBaseSlasher} from "@symbioticfi/core/src/interfaces/slasher/IBaseSlasher.sol";
import {IVetoSlasher} from "@symbioticfi/core/src/interfaces/slasher/IVetoSlasher.sol";
// ...
address VAULT_CONFIGURATOR = 0x0000000000000000000000000000000000000000; // address of the VaultConfigurator (see Deployments page)
// ...
address[] memory networkLimitSetRoleHolders = new address[](1);
networkLimitSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
address[] memory operatorNetworkSharesSetRoleHolders = new address[](1);
operatorNetworkSharesSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
(address vault, address networkRestakeDelegator, address vetoSlasher) = IVaultConfigurator(VAULT_CONFIGURATOR).create(
IVaultConfigurator.InitParams({
version: 1, // Vault’s version (= common one)
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s owner (can migrate the Vault to new versions in the future)
vaultParams: abi.encode(IVault.InitParams({
collateral: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, // address of the collateral - wstETH
burner: <BURNER_ROUTER>, // address of the deployed burner router
epochDuration: 604800, // duration of the Vault epoch in seconds (= 7 days)
depositWhitelist: false, // if enable deposit whitelisting
isDepositLimit: false, // if enable deposit limit
depositLimit: 0, // deposit limit
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s admin (can manage all roles)
depositWhitelistSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit whitelisting
depositorWhitelistRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the depositors whitelister
isDepositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit limit
depositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the deposit limit setter
})),
delegatorIndex: 0, // Delegator’s type (= NetworkRestakeDelegator)
delegatorParams: abi.encode(INetworkRestakeDelegator.InitParams({
baseParams: IBaseDelegator.BaseParams({
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Delegator’s admin (can manage all roles)
hook: 0x0000000000000000000000000000000000000000, // address of the hook (if not zero, receives onSlash() call on each slashing)
hookSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the hook setter
}),
networkLimitSetRoleHolders: networkLimitSetRoleHolders, // array of addresses of the network limit setters
operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders // array of addresses of the operator-network shares setters
})),
withSlasher: true, // if enable Slasher module
slasherIndex: 1, // Slasher’s type (= VetoSlasher)
slasherParams: abi.encode(IVetoSlasher.InitParams({
baseParams: IBaseSlasher.BaseParams({
isBurnerHook: true // if enable the `burner` to receive onSlash() call after each slashing (is needed for the burner router workflow)
}),
vetoDuration: 86400, // veto duration (= 1 day)
resolverSetEpochsDelay: 3 // number of Vault epochs needed for the resolver to be changed
}))
})
);
// ...
import {IVaultConfigurator} from "@symbioticfi/core/src/interfaces/IVaultConfigurator.sol";
import {IVault} from "@symbioticfi/core/src/interfaces/vault/IVault.sol";
import {IBaseDelegator} from "@symbioticfi/core/src/interfaces/delegator/IBaseDelegator.sol";
import {INetworkRestakeDelegator} from "@symbioticfi/core/src/interfaces/delegator/INetworkRestakeDelegator.sol";
import {IBaseSlasher} from "@symbioticfi/core/src/interfaces/slasher/IBaseSlasher.sol";
import {IVetoSlasher} from "@symbioticfi/core/src/interfaces/slasher/IVetoSlasher.sol";
// ...
address VAULT_CONFIGURATOR = 0xD2191FE92987171691d552C219b8caEf186eb9cA; // address of the VaultConfigurator (see Deployments page)
// ...
address[] memory networkLimitSetRoleHolders = new address[](1);
networkLimitSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
address[] memory operatorNetworkSharesSetRoleHolders = new address[](1);
operatorNetworkSharesSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
(address vault, address networkRestakeDelegator, address vetoSlasher) = IVaultConfigurator(VAULT_CONFIGURATOR).create(
IVaultConfigurator.InitParams({
version: 1, // Vault’s version (= common one)
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s owner (can migrate the Vault to new versions in the future)
vaultParams: abi.encode(IVault.InitParams({
collateral: 0x8d09a4502Cc8Cf1547aD300E066060D043f6982D, // address of the collateral - wstETH
burner: <BURNER_ROUTER>, // address of the deployed burner router
epochDuration: 604800, // duration of the Vault epoch in seconds (= 7 days)
depositWhitelist: false, // if enable deposit whitelisting
isDepositLimit: false, // if enable deposit limit
depositLimit: 0, // deposit limit
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s admin (can manage all roles)
depositWhitelistSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit whitelisting
depositorWhitelistRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the depositors whitelister
isDepositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit limit
depositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the deposit limit setter
})),
delegatorIndex: 0, // Delegator’s type (= NetworkRestakeDelegator)
delegatorParams: abi.encode(INetworkRestakeDelegator.InitParams({
baseParams: IBaseDelegator.BaseParams({
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Delegator’s admin (can manage all roles)
hook: 0x0000000000000000000000000000000000000000, // address of the hook (if not zero, receives onSlash() call on each slashing)
hookSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the hook setter
}),
networkLimitSetRoleHolders: networkLimitSetRoleHolders, // array of addresses of the network limit setters
operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders // array of addresses of the operator-network shares setters
})),
withSlasher: true, // if enable Slasher module
slasherIndex: 1, // Slasher’s type (= VetoSlasher)
slasherParams: abi.encode(IVetoSlasher.InitParams({
baseParams: IBaseSlasher.BaseParams({
isBurnerHook: true // if enable the `burner` to receive onSlash() call after each slashing (is needed for the burner router workflow)
}),
vetoDuration: 86400, // veto duration (= 1 day)
resolverSetEpochsDelay: 3 // number of Vault epochs needed for the resolver to be changed
}))
})
);
// ...
import {IVaultConfigurator} from "@symbioticfi/core/src/interfaces/IVaultConfigurator.sol";
import {IVault} from "@symbioticfi/core/src/interfaces/vault/IVault.sol";
import {IBaseDelegator} from "@symbioticfi/core/src/interfaces/delegator/IBaseDelegator.sol";
import {INetworkRestakeDelegator} from "@symbioticfi/core/src/interfaces/delegator/INetworkRestakeDelegator.sol";
import {IBaseSlasher} from "@symbioticfi/core/src/interfaces/slasher/IBaseSlasher.sol";
import {IVetoSlasher} from "@symbioticfi/core/src/interfaces/slasher/IVetoSlasher.sol";
// ...
address VAULT_CONFIGURATOR = 0xD2191FE92987171691d552C219b8caEf186eb9cA; // address of the VaultConfigurator (see Deployments page)
// ...
address[] memory networkLimitSetRoleHolders = new address[](1);
networkLimitSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
address[] memory operatorNetworkSharesSetRoleHolders = new address[](1);
operatorNetworkSharesSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
(address vault, address networkRestakeDelegator, address vetoSlasher) = IVaultConfigurator(VAULT_CONFIGURATOR).create(
IVaultConfigurator.InitParams({
version: 1, // Vault’s version (= common one)
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s owner (can migrate the Vault to new versions in the future)
vaultParams: abi.encode(IVault.InitParams({
collateral: 0xB82381A3fBD3FaFA77B3a7bE693342618240067b, // address of the collateral - wstETH
burner: <BURNER_ROUTER>, // address of the deployed burner router
epochDuration: 604800, // duration of the Vault epoch in seconds (= 7 days)
depositWhitelist: false, // if enable deposit whitelisting
isDepositLimit: false, // if enable deposit limit
depositLimit: 0, // deposit limit
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s admin (can manage all roles)
depositWhitelistSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit whitelisting
depositorWhitelistRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the depositors whitelister
isDepositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit limit
depositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the deposit limit setter
})),
delegatorIndex: 0, // Delegator’s type (= NetworkRestakeDelegator)
delegatorParams: abi.encode(INetworkRestakeDelegator.InitParams({
baseParams: IBaseDelegator.BaseParams({
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Delegator’s admin (can manage all roles)
hook: 0x0000000000000000000000000000000000000000, // address of the hook (if not zero, receives onSlash() call on each slashing)
hookSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the hook setter
}),
networkLimitSetRoleHolders: networkLimitSetRoleHolders, // array of addresses of the network limit setters
operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders // array of addresses of the operator-network shares setters
})),
withSlasher: true, // if enable Slasher module
slasherIndex: 1, // Slasher’s type (= VetoSlasher)
slasherParams: abi.encode(IVetoSlasher.InitParams({
baseParams: IBaseSlasher.BaseParams({
isBurnerHook: true // if enable the `burner` to receive onSlash() call after each slashing (is needed for the burner router workflow)
}),
vetoDuration: 86400, // veto duration (= 1 day)
resolverSetEpochsDelay: 3 // number of Vault epochs needed for the resolver to be changed
}))
})
);
// ...
3. Staker rewards
Rewards logic is not enshrined in the core contract, and we allow anyone to create their own implementations if needed. However, it requires resources like time, knowledge, and money. Therefore, we provide a default implementation of the staker rewards named DefaultStakerRewards. Its main goals are:
- allow networks to distribute rewards for the stakers of the certain Vault
- allow stakers to claim these rewards
- allow Vault curators to receive fees from the distributed rewards
It is the only staker rewards implementation we provided, so let’s add it to our deployment configuration.
There is no connection logic in the Vault with the rewards contract, so it will be handled off-chain (see details).
Staker rewards deployment
- Clone the rewards contracts repository by running the following command:
git clone --recurse-submodules https://github.com/symbioticfi/rewards.git
- Navigate into the cloned repository folder:
cd rewards
- Deploy a staker rewards contract using a simple script:
It is an example command, meaning you need to replace the given values with necessary ones.
In addition, you need to choose a preferred wallet option and adjust the command accordingly.
- Mainnet
- Holesky
- Sepolia
DEFAULT_STAKER_REWARDS_FACTORY=0x0000000000000000000000000000000000000000 # address of the DefaultStakerRewardsFactory (see Deployments page)
VAULT=<VAULT> # address of the deployed Vault
ADMIN_FEE=1000 # admin fee percent to get from all the rewards distributions (10% = 1_000 | 100% = 10_000)
ADMIN=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the main admin (can manage all roles)
ADMIN_FEE_CLAIMER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the admin fee claimer
ADMIN_FEE_SETTER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the admin fee setter
forge script script/deploy/DefaultStakerRewards.s.sol:DefaultStakerRewardsScript \
$DEFAULT_STAKER_REWARDS_FACTORY \
$VAULT \
$ADMIN_FEE \
$ADMIN \
$ADMIN_FEE_CLAIMER \
$ADMIN_FEE_SETTER \
--sig "run(address,address,uint256,address,address,address)" \
--rpc-url=https://ethereum-rpc.publicnode.com \
--chain mainnet \
--broadcast
DEFAULT_STAKER_REWARDS_FACTORY=0x698C36DE44D73AEfa3F0Ce3c0255A8667bdE7cFD # address of the DefaultStakerRewardsFactory (see Deployments page)
VAULT=<VAULT> # address of the deployed Vault
ADMIN_FEE=1000 # admin fee percent to get from all the rewards distributions (10% = 1_000 | 100% = 10_000)
ADMIN=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the main admin (can manage all roles)
ADMIN_FEE_CLAIMER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the admin fee claimer
ADMIN_FEE_SETTER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the admin fee setter
forge script script/deploy/DefaultStakerRewards.s.sol:DefaultStakerRewardsScript \
$DEFAULT_STAKER_REWARDS_FACTORY \
$VAULT \
$ADMIN_FEE \
$ADMIN \
$ADMIN_FEE_CLAIMER \
$ADMIN_FEE_SETTER \
--sig "run(address,address,uint256,address,address,address)" \
--rpc-url=https://ethereum-holesky-rpc.publicnode.com \
--chain holesky \
--broadcast
DEFAULT_STAKER_REWARDS_FACTORY=0x70C618a13D1A57f7234c0b893b9e28C5cA8E7f37 # address of the DefaultStakerRewardsFactory (see Deployments page)
VAULT=<VAULT> # address of the deployed Vault
ADMIN_FEE=1000 # admin fee percent to get from all the rewards distributions (10% = 1_000 | 100% = 10_000)
ADMIN=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the main admin (can manage all roles)
ADMIN_FEE_CLAIMER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the admin fee claimer
ADMIN_FEE_SETTER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the admin fee setter
forge script script/deploy/DefaultStakerRewards.s.sol:DefaultStakerRewardsScript \
$DEFAULT_STAKER_REWARDS_FACTORY \
$VAULT \
$ADMIN_FEE \
$ADMIN \
$ADMIN_FEE_CLAIMER \
$ADMIN_FEE_SETTER \
--sig "run(address,address,uint256,address,address,address)" \
--rpc-url=https://ethereum-sepolia-rpc.publicnode.com \
--chain sepolia \
--broadcast
Staker rewards deployment - technical (Solidity)
The following instruction assumes you have an already initialized Foundry repository (read more here).
- Install the rewards contracts repository by running the following command:
forge install symbioticfi/rewards
- Update (or create if not yet) a
remappings.txt
file inside your repository accordingly:
...
@symbioticfi/rewards/=lib/rewards/
- Create a staker rewards contract using a DefaultStakerRewardsFactory contract:
It is an example code snippet, meaning you need to replace the given values with necessary ones.
- Mainnet
- Holesky
- Sepolia
import {IDefaultStakerRewardsFactory} from "@symbioticfi/rewards/src/interfaces/defaultStakerRewards/IDefaultStakerRewardsFactory.sol";
import {IDefaultStakerRewards} from "@symbioticfi/rewards/src/interfaces/defaultStakerRewards/IDefaultStakerRewards.sol";
// ...
address DEFAULT_STAKER_REWARDS_FACTORY = 0x0000000000000000000000000000000000000000; // address of the DefaultStakerRewardsFactory (see Deployments page)
// ...
address defaultStakerRewards = IDefaultStakerRewardsFactory(DEFAULT_STAKER_REWARDS_FACTORY).create(IDefaultStakerRewards.InitParams({
vault: <VAULT>, // address of the deployed Vault
adminFee: 1000, // admin fee percent to get from all the rewards distributions (10% = 1_000 | 100% = 10_000)
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the main admin (can manage all roles)
adminFeeClaimRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the admin fee claimer
adminFeeSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the admin fee setter
}));
// ...
import {IDefaultStakerRewardsFactory} from "@symbioticfi/rewards/src/interfaces/defaultStakerRewards/IDefaultStakerRewardsFactory.sol";
import {IDefaultStakerRewards} from "@symbioticfi/rewards/src/interfaces/defaultStakerRewards/IDefaultStakerRewards.sol";
// ...
address DEFAULT_STAKER_REWARDS_FACTORY = 0x698C36DE44D73AEfa3F0Ce3c0255A8667bdE7cFD; // address of the DefaultStakerRewardsFactory (see Deployments page)
// ...
address defaultStakerRewards = IDefaultStakerRewardsFactory(DEFAULT_STAKER_REWARDS_FACTORY).create(IDefaultStakerRewards.InitParams({
vault: <VAULT>, // address of the deployed Vault
adminFee: 1000, // admin fee percent to get from all the rewards distributions (10% = 1_000 | 100% = 10_000)
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the main admin (can manage all roles)
adminFeeClaimRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the admin fee claimer
adminFeeSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the admin fee setter
}));
// ...
import {IDefaultStakerRewardsFactory} from "@symbioticfi/rewards/src/interfaces/defaultStakerRewards/IDefaultStakerRewardsFactory.sol";
import {IDefaultStakerRewards} from "@symbioticfi/rewards/src/interfaces/defaultStakerRewards/IDefaultStakerRewards.sol";
// ...
address DEFAULT_STAKER_REWARDS_FACTORY = 0x70C618a13D1A57f7234c0b893b9e28C5cA8E7f37; // address of the DefaultStakerRewardsFactory (see Deployments page)
// ...
address defaultStakerRewards = IDefaultStakerRewardsFactory(DEFAULT_STAKER_REWARDS_FACTORY).create(IDefaultStakerRewards.InitParams({
vault: <VAULT>, // address of the deployed Vault
adminFee: 1000, // admin fee percent to get from all the rewards distributions (10% = 1_000 | 100% = 10_000)
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the main admin (can manage all roles)
adminFeeClaimRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the admin fee claimer
adminFeeSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the admin fee setter
}));
// ...
Advanced
The further text describes extended configurations that may bring risk reductions, wider support of the networks, and more capital efficiency for users.
Network-specific burners
Such a case is possible:
- In general, a Vault wants the slashed funds to be burnt.
- On the other hand, there is a highly reputable network that wants its slashings to be redistributed across the users who could suffer losses because of the operators’ malicious activity.
- The Vault decides to integrate such a network to increase its capital efficiency.
Right now, our configuration doesn’t contain such logic, but it can already support it. As mentioned earlier, BurnerRouter allows splitting the slashed funds across any number of receivers depending on the slashing networks and the slashed operators. Therefore, given the network address that needs such functionality and the burner address to send the funds to, it is possible to either configure such behavior using an already existing burner router or take care of it during the deployment.
In general, burning the slashed funds is one of the safest ways to go. Any other methodology for processing the slashed funds may create incentives for different parties to trigger the slashing, depending on many factors.
Burner Router with preconfigured network-specific burner deployment
- Clone the burners repository by running the following command:
git clone --recurse-submodules https://github.com/symbioticfi/burners.git
- Navigate into the cloned repository folder:
cd burners
- Deploy a burner router contract with a preconfigured network-specific burner using a simple script:
It is an example command, meaning you need to replace the given values with necessary ones.
In addition, you need to choose a preferred wallet option and adjust the command accordingly.
- Mainnet
- Holesky
- Sepolia
BURNER_ROUTER_FACTORY=0x0000000000000000000000000000000000000000 # address of the BurnerRouterFactory (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the router’s owner
COLLATERAL=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 # address of the collateral - wstETH (MUST be the same as for the Vault to connect)
DELAY=1814400 # duration of the receivers’ update delay (= 21 days)
GLOBAL_RECEIVER=0x0000000000000000000000000000000000000000 # address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
NETWORK_RECEIVERS=[\(<NETWORK_ADDRESS>,<RECEIVER_ADDRESS>\)] # array with elements like "\(network_address,receiver_address\)" meaning network-specific receivers
OPERATOR_NETWORK_RECEIVERS=[] # array with elements like "\(network_address,operator_address,receiver_address\)" meaning operator-network-specific receivers
forge script script/deploy/BurnerRouter.s.sol:BurnerRouterScript \
$BURNER_ROUTER_FACTORY \
$OWNER \
$COLLATERAL \
$DELAY \
$GLOBAL_RECEIVER \
$NETWORK_RECEIVERS \
$OPERATOR_NETWORK_RECEIVERS \
--sig "run(address,address,address,uint48,address,(address,address)[],(address,address,address)[])" \
--rpc-url=https://ethereum-rpc.publicnode.com \
--chain mainnet \
--broadcast
BURNER_ROUTER_FACTORY=0x32e2AfbdAffB1e675898ABA75868d92eE1E68f3b # address of the BurnerRouterFactory (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the router’s owner
COLLATERAL=0x8d09a4502Cc8Cf1547aD300E066060D043f6982D # address of the collateral - wstETH (MUST be the same as for the Vault to connect)
DELAY=1814400 # duration of the receivers’ update delay (= 21 days)
GLOBAL_RECEIVER=0x25133c2c49A343F8312bb6e896C1ea0Ad8CD0EBd # address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
NETWORK_RECEIVERS=[\(<NETWORK_ADDRESS>,<RECEIVER_ADDRESS>\)] # array with elements like "\(network_address,receiver_address\)" meaning network-specific receivers
OPERATOR_NETWORK_RECEIVERS=[] # array with elements like "\(network_address,operator_address,receiver_address\)" meaning operator-network-specific receivers
forge script script/deploy/BurnerRouter.s.sol:BurnerRouterScript \
$BURNER_ROUTER_FACTORY \
$OWNER \
$COLLATERAL \
$DELAY \
$GLOBAL_RECEIVER \
$NETWORK_RECEIVERS \
$OPERATOR_NETWORK_RECEIVERS \
--sig "run(address,address,address,uint48,address,(address,address)[],(address,address,address)[])" \
--rpc-url=https://ethereum-holesky-rpc.publicnode.com \
--chain holesky \
--broadcast
BURNER_ROUTER_FACTORY=0x32e2AfbdAffB1e675898ABA75868d92eE1E68f3b # address of the BurnerRouterFactory (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the router’s owner
COLLATERAL=0xB82381A3fBD3FaFA77B3a7bE693342618240067b # address of the collateral - wstETH (MUST be the same as for the Vault to connect)
DELAY=1814400 # duration of the receivers’ update delay (= 21 days)
GLOBAL_RECEIVER=0x58D347334A5E6bDE7279696abE59a11873294FA4 # address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
NETWORK_RECEIVERS=[\(<NETWORK_ADDRESS>,<RECEIVER_ADDRESS>\)] # array with elements like "\(network_address,receiver_address\)" meaning network-specific receivers
OPERATOR_NETWORK_RECEIVERS=[] # array with elements like "\(network_address,operator_address,receiver_address\)" meaning operator-network-specific receivers
forge script script/deploy/BurnerRouter.s.sol:BurnerRouterScript \
$BURNER_ROUTER_FACTORY \
$OWNER \
$COLLATERAL \
$DELAY \
$GLOBAL_RECEIVER \
$NETWORK_RECEIVERS \
$OPERATOR_NETWORK_RECEIVERS \
--sig "run(address,address,address,uint48,address,(address,address)[],(address,address,address)[])" \
--rpc-url=https://ethereum-sepolia-rpc.publicnode.com \
--chain sepolia \
--broadcast
Burner Router with preconfigured network-specific burner deployment - technical (Solidity)
The following instruction assumes you have an already initialized Foundry repository (read more here).
- Install the burners repository by running the following command:
forge install symbioticfi/burners
- Update (or create if not yet) a
remappings.txt
file inside your repository accordingly:
...
@symbioticfi/burners/=lib/burners/
- Create a burner router contract using a BurnerRouterFactory contract:
It is an example code snippet, meaning you need to replace the given values with necessary ones.
- Mainnet
- Holesky
- Sepolia
import {IBurnerRouterFactory} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouterFactory.sol";
import {IBurnerRouter} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouter.sol";
// ...
address BURNER_ROUTER_FACTORY = 0x0000000000000000000000000000000000000000; // address of the BurnerRouterFactory (see Deployments page)
// ...
IBurnerRouter.NetworkReceiver[] memory networkReceivers = new IBurnerRouter.NetworkReceiver[](1);
networkReceivers[0] = IBurnerRouter.NetworkReceiver({
network: <NETWORK_ADDRESS>,
receiver: <RECEIVER_ADDRESS>
});
address burnerRouter = IBurnerRouterFactory(BURNER_ROUTER_FACTORY).create(
IBurnerRouter.InitParams({
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the router’s owner
collateral: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, // address of the collateral - wstETH (MUST be the same as for the Vault to connect)
delay: 1814400, // duration of the receivers’ update delay (= 21 days)
globalReceiver: 0x0000000000000000000000000000000000000000, // address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
networkReceivers: networkReceivers, // array with IBurnerRouter.NetworkReceiver elements meaning network-specific receivers
operatorNetworkReceivers: new IBurnerRouter.OperatorNetworkReceiver[](0) // array with IBurnerRouter.OperatorNetworkReceiver elements meaning network-specific receivers
})
);
// ...
import {IBurnerRouterFactory} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouterFactory.sol";
import {IBurnerRouter} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouter.sol";
// ...
address BURNER_ROUTER_FACTORY = 0x32e2AfbdAffB1e675898ABA75868d92eE1E68f3b; // address of the BurnerRouterFactory (see Deployments page)
// ...
IBurnerRouter.NetworkReceiver[] memory networkReceivers = new IBurnerRouter.NetworkReceiver[](1);
networkReceivers[0] = IBurnerRouter.NetworkReceiver({
network: <NETWORK_ADDRESS>,
receiver: <RECEIVER_ADDRESS>
});
address burnerRouter = IBurnerRouterFactory(BURNER_ROUTER_FACTORY).create(
IBurnerRouter.InitParams({
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the router’s owner
collateral: 0x8d09a4502Cc8Cf1547aD300E066060D043f6982D, // address of the collateral - wstETH (MUST be the same as for the Vault to connect)
delay: 1814400, // duration of the receivers’ update delay (= 21 days)
globalReceiver: 0x25133c2c49A343F8312bb6e896C1ea0Ad8CD0EBd, // address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
networkReceivers: networkReceivers, // array with IBurnerRouter.NetworkReceiver elements meaning network-specific receivers
operatorNetworkReceivers: new IBurnerRouter.OperatorNetworkReceiver[](0) // array with IBurnerRouter.OperatorNetworkReceiver elements meaning network-specific receivers
})
);
// ...
import {IBurnerRouterFactory} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouterFactory.sol";
import {IBurnerRouter} from "@symbioticfi/burners/src/interfaces/router/IBurnerRouter.sol";
// ...
address BURNER_ROUTER_FACTORY = 0x32e2AfbdAffB1e675898ABA75868d92eE1E68f3b; // address of the BurnerRouterFactory (see Deployments page)
// ...
IBurnerRouter.NetworkReceiver[] memory networkReceivers = new IBurnerRouter.NetworkReceiver[](1);
networkReceivers[0] = IBurnerRouter.NetworkReceiver({
network: <NETWORK_ADDRESS>,
receiver: <RECEIVER_ADDRESS>
});
address burnerRouter = IBurnerRouterFactory(BURNER_ROUTER_FACTORY).create(
IBurnerRouter.InitParams({
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the router’s owner
collateral: 0xB82381A3fBD3FaFA77B3a7bE693342618240067b, // address of the collateral - wstETH (MUST be the same as for the Vault to connect)
delay: 1814400, // duration of the receivers’ update delay (= 21 days)
globalReceiver: 0x58D347334A5E6bDE7279696abE59a11873294FA4, // address of the pure burner corresponding to the collateral - wstETH_Burner (some collaterals are covered by us; see Deployments page)
networkReceivers: networkReceivers, // array with IBurnerRouter.NetworkReceiver elements meaning network-specific receivers
operatorNetworkReceivers: new IBurnerRouter.OperatorNetworkReceiver[](0) // array with IBurnerRouter.OperatorNetworkReceiver elements meaning network-specific receivers
})
);
// ...
Configure Burner Router with network-specific burner after deployment
Generally, all the changes inside the burner router can be performed in 2 steps:
- Create a pending request to change some value
- Accept the request after waiting for a configured
delay
Therefore, to set a new receiver (which can be a network-specific burner or treasury or any address) for a particular network the further steps are needed:
- Owner calls
setNetworkReceiver(network, receiver)
- Wait for
delay
- Anyone commits the change via
acceptNetworkReceiver(network)
To set a new receiver for some exact operator inside a particular network:
- Owner calls
setOperatorNetworkReceiver(network, operator, receiver)
- Wait for
delay
- Anyone commits the change via
acceptOperatorNetworkReceiver(network)
To set a new global receiver that gets funds if no specific receivers for a network-operator pair are configured:
- Owner calls
setGlobalReceiver(receiver)
- Wait for
delay
- Anyone commits the change via
acceptGlobalReceiver(network)
To change a delay
:
- Owner calls
setDelay(newDelay)
- Wait for an old
delay
- Anyone commits the change via
acceptDelay()
Hook
Hook is a contract that the Vault curator can set to receive onSlash()
call on each slashing event. Symbiotic doesn’t specify any logic behind it. However, the basic idea of the hook is to allow the creation of any delegation adjustment mechanic during the slashing.
Symbiotic Delegator contracts don’t update allocations on the slashing events in any way. This means that a malicious network or an operator may need only several blocks (depending on their allocated stake) to slash all of Vault’s funds.
We provide pure example implementations of some standard adjustment mechanics for each Delegator type - here.
Core modules deployment with hook
- Clone the core contracts repository by running the following command:
git clone --recurse-submodules https://github.com/symbioticfi/core.git
- Navigate into the cloned repository folder:
cd core
- Deploy core modules contracts using a simple script:
It is an example command, meaning you need to replace the given values with necessary ones.
In addition, you need to choose a preferred wallet option and adjust the command accordingly.
- Mainnet
- Holesky
- Sepolia
VAULT_CONFIGURATOR=0x0000000000000000000000000000000000000000 # address of the VaultConfigurator (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the deploying contracts’ owner/curator
COLLATERAL=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 # address of the collateral - wstETH
BURNER=<BURNER_ROUTER> # address of the deployed burner router
EPOCH_DURATION=604800 # duration of the Vault epoch in seconds (= 7 days)
ENABLE_WHITELIST=false # if enable deposit whitelisting
DEPOSIT_LIMIT=0 # deposit limit (not enabled in case of 0)
DELEGATOR_TYPE=0 # Delegator’s type (= NetworkRestakeDelegator)
HOOK=<HOOK> # address of the hook (if not zero, is called via `onSlash()` function on each slashing)
ENABLE_SLASHER=true # if enable Slasher module
SLASHER_TYPE=1 # Slasher’s type (= VetoSlasher)
VETO_DURATION=86400 # veto duration (= 1 day)
forge script script/deploy/Vault.s.sol:VaultScript \
$VAULT_CONFIGURATOR \
$OWNER \
$COLLATERAL \
$BURNER \
$EPOCH_DURATION \
$ENABLE_WHITELIST \
$DEPOSIT_LIMIT \
$DELEGATOR_TYPE \
$HOOK \
$ENABLE_SLASHER \
$SLASHER_TYPE \
$VETO_DURATION \
--sig "run(address,address,address,address,uint48,bool,uint256,uint64,address,bool,uint64,uint48)" \
--rpc-url=https://ethereum-rpc.publicnode.com \
--chain mainnet \
--broadcast
VAULT_CONFIGURATOR=0xD2191FE92987171691d552C219b8caEf186eb9cA # address of the VaultConfigurator (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the deploying contracts’ owner/curator
COLLATERAL=0x8d09a4502Cc8Cf1547aD300E066060D043f6982D # address of the collateral - wstETH
BURNER=<BURNER_ROUTER> # address of the deployed burner router
EPOCH_DURATION=604800 # duration of the Vault epoch in seconds (= 7 days)
ENABLE_WHITELIST=false # if enable deposit whitelisting
DEPOSIT_LIMIT=0 # deposit limit (not enabled in case of 0)
DELEGATOR_TYPE=0 # Delegator’s type (= NetworkRestakeDelegator)
HOOK=<HOOK> # address of the hook (if not zero, is called via `onSlash()` function on each slashing)
ENABLE_SLASHER=true # if enable Slasher module
SLASHER_TYPE=1 # Slasher’s type (= VetoSlasher)
VETO_DURATION=86400 # veto duration (= 1 day)
forge script script/deploy/Vault.s.sol:VaultScript \
$VAULT_CONFIGURATOR \
$OWNER \
$COLLATERAL \
$BURNER \
$EPOCH_DURATION \
$ENABLE_WHITELIST \
$DEPOSIT_LIMIT \
$DELEGATOR_TYPE \
$HOOK \
$ENABLE_SLASHER \
$SLASHER_TYPE \
$VETO_DURATION \
--sig "run(address,address,address,address,uint48,bool,uint256,uint64,address,bool,uint64,uint48)" \
--rpc-url=https://ethereum-holesky-rpc.publicnode.com \
--chain holesky \
--broadcast
VAULT_CONFIGURATOR=0xD2191FE92987171691d552C219b8caEf186eb9cA # address of the VaultConfigurator (see Deployments page)
OWNER=0xe8616DEcea16b5216e805B0b8caf7784de7570E7 # address of the deploying contracts’ owner/curator
COLLATERAL=0xB82381A3fBD3FaFA77B3a7bE693342618240067b # address of the collateral - wstETH
BURNER=<BURNER_ROUTER> # address of the deployed burner router
EPOCH_DURATION=604800 # duration of the Vault epoch in seconds (= 7 days)
ENABLE_WHITELIST=false # if enable deposit whitelisting
DEPOSIT_LIMIT=0 # deposit limit (not enabled in case of 0)
DELEGATOR_TYPE=0 # Delegator’s type (= NetworkRestakeDelegator)
HOOK=<HOOK> # address of the hook (if not zero, is called via `onSlash()` function on each slashing)
ENABLE_SLASHER=true # if enable Slasher module
SLASHER_TYPE=1 # Slasher’s type (= VetoSlasher)
VETO_DURATION=86400 # veto duration (= 1 day)
forge script script/deploy/Vault.s.sol:VaultScript \
$VAULT_CONFIGURATOR \
$OWNER \
$COLLATERAL \
$BURNER \
$EPOCH_DURATION \
$ENABLE_WHITELIST \
$DEPOSIT_LIMIT \
$DELEGATOR_TYPE \
$HOOK \
$ENABLE_SLASHER \
$SLASHER_TYPE \
$VETO_DURATION \
--sig "run(address,address,address,address,uint48,bool,uint256,uint64,address,bool,uint64,uint48)" \
--rpc-url=https://ethereum-sepolia-rpc.publicnode.com \
--chain sepolia \
--broadcast
Core modules deployment with hook - technical (Solidity)
The following instruction assumes you have an already initialized Foundry repository (read more here).
- Install the core contracts repository by running the following command:
forge install symbioticfi/core
- Update (or create if not yet) a
remappings.txt
file inside your repository accordingly:
...
@symbioticfi/core/=lib/core/
- Create core modules using a VaultConfigurator contract:
It is an example code snippet, meaning you need to replace the given values with necessary ones.
- Mainnet
- Holesky
- Sepolia
import {IVaultConfigurator} from "@symbioticfi/core/src/interfaces/IVaultConfigurator.sol";
import {IVault} from "@symbioticfi/core/src/interfaces/vault/IVault.sol";
import {IBaseDelegator} from "@symbioticfi/core/src/interfaces/delegator/IBaseDelegator.sol";
import {INetworkRestakeDelegator} from "@symbioticfi/core/src/interfaces/delegator/INetworkRestakeDelegator.sol";
import {IBaseSlasher} from "@symbioticfi/core/src/interfaces/slasher/IBaseSlasher.sol";
import {IVetoSlasher} from "@symbioticfi/core/src/interfaces/slasher/IVetoSlasher.sol";
// ...
address VAULT_CONFIGURATOR = 0x0000000000000000000000000000000000000000; // address of the VaultConfigurator (see Deployments page)
// ...
address hook = <HOOK>;
address[] memory networkLimitSetRoleHolders = new address[](2);
networkLimitSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
networkLimitSetRoleHolders[1] = hook;
address[] memory operatorNetworkSharesSetRoleHolders = new address[](2);
operatorNetworkSharesSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
operatorNetworkSharesSetRoleHolders[1] = hook;
(address vault, address networkRestakeDelegator, address vetoSlasher) = IVaultConfigurator(VAULT_CONFIGURATOR).create(
IVaultConfigurator.InitParams({
version: 1, // Vault’s version (= common one)
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s owner (can migrate the Vault to new versions in the future)
vaultParams: abi.encode(IVault.InitParams({
collateral: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, // address of the collateral - wstETH
burner: <BURNER_ROUTER>, // address of the deployed burner router
epochDuration: 604800, // duration of the Vault epoch in seconds (= 7 days)
depositWhitelist: false, // if enable deposit whitelisting
isDepositLimit: false, // if enable deposit limit
depositLimit: 0, // deposit limit
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s admin (can manage all roles)
depositWhitelistSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit whitelisting
depositorWhitelistRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the depositors whitelister
isDepositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit limit
depositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the deposit limit setter
})),
delegatorIndex: 0, // Delegator’s type (= NetworkRestakeDelegator)
delegatorParams: abi.encode(INetworkRestakeDelegator.InitParams({
baseParams: IBaseDelegator.BaseParams({
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Delegator’s admin (can manage all roles)
hook: hook, // address of the hook (if not zero, receives onSlash() call on each slashing)
hookSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the hook setter
}),
networkLimitSetRoleHolders: networkLimitSetRoleHolders, // array of addresses of the network limit setters
operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders // array of addresses of the operator-network shares setters
})),
withSlasher: true, // if enable Slasher module
slasherIndex: 1, // Slasher’s type (= VetoSlasher)
slasherParams: abi.encode(IVetoSlasher.InitParams({
baseParams: IBaseSlasher.BaseParams({
isBurnerHook: true // if enable the `burner` to receive onSlash() call after each slashing (is needed for the burner router workflow)
}),
vetoDuration: 86400, // veto duration (= 1 day)
resolverSetEpochsDelay: 3 // number of Vault epochs needed for the resolver to be changed
}))
})
);
// ...
import {IVaultConfigurator} from "@symbioticfi/core/src/interfaces/IVaultConfigurator.sol";
import {IVault} from "@symbioticfi/core/src/interfaces/vault/IVault.sol";
import {IBaseDelegator} from "@symbioticfi/core/src/interfaces/delegator/IBaseDelegator.sol";
import {INetworkRestakeDelegator} from "@symbioticfi/core/src/interfaces/delegator/INetworkRestakeDelegator.sol";
import {IBaseSlasher} from "@symbioticfi/core/src/interfaces/slasher/IBaseSlasher.sol";
import {IVetoSlasher} from "@symbioticfi/core/src/interfaces/slasher/IVetoSlasher.sol";
// ...
address VAULT_CONFIGURATOR = 0x0000000000000000000000000000000000000000; // address of the VaultConfigurator (see Deployments page)
// ...
address hook = <HOOK>;
address[] memory networkLimitSetRoleHolders = new address[](2);
networkLimitSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
networkLimitSetRoleHolders[1] = hook;
address[] memory operatorNetworkSharesSetRoleHolders = new address[](2);
operatorNetworkSharesSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
operatorNetworkSharesSetRoleHolders[1] = hook;
(address vault, address networkRestakeDelegator, address vetoSlasher) = IVaultConfigurator(VAULT_CONFIGURATOR).create(
IVaultConfigurator.InitParams({
version: 1, // Vault’s version (= common one)
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s owner (can migrate the Vault to new versions in the future)
vaultParams: abi.encode(IVault.InitParams({
collateral: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, // address of the collateral - wstETH
burner: <BURNER_ROUTER>, // address of the deployed burner router
epochDuration: 604800, // duration of the Vault epoch in seconds (= 7 days)
depositWhitelist: false, // if enable deposit whitelisting
isDepositLimit: false, // if enable deposit limit
depositLimit: 0, // deposit limit
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s admin (can manage all roles)
depositWhitelistSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit whitelisting
depositorWhitelistRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the depositors whitelister
isDepositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit limit
depositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the deposit limit setter
})),
delegatorIndex: 0, // Delegator’s type (= NetworkRestakeDelegator)
delegatorParams: abi.encode(INetworkRestakeDelegator.InitParams({
baseParams: IBaseDelegator.BaseParams({
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Delegator’s admin (can manage all roles)
hook: hook, // address of the hook (if not zero, receives onSlash() call on each slashing)
hookSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the hook setter
}),
networkLimitSetRoleHolders: networkLimitSetRoleHolders, // array of addresses of the network limit setters
operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders // array of addresses of the operator-network shares setters
})),
withSlasher: true, // if enable Slasher module
slasherIndex: 1, // Slasher’s type (= VetoSlasher)
slasherParams: abi.encode(IVetoSlasher.InitParams({
baseParams: IBaseSlasher.BaseParams({
isBurnerHook: true // if enable the `burner` to receive onSlash() call after each slashing (is needed for the burner router workflow)
}),
vetoDuration: 86400, // veto duration (= 1 day)
resolverSetEpochsDelay: 3 // number of Vault epochs needed for the resolver to be changed
}))
})
);
// ...
import {IVaultConfigurator} from "@symbioticfi/core/src/interfaces/IVaultConfigurator.sol";
import {IVault} from "@symbioticfi/core/src/interfaces/vault/IVault.sol";
import {IBaseDelegator} from "@symbioticfi/core/src/interfaces/delegator/IBaseDelegator.sol";
import {INetworkRestakeDelegator} from "@symbioticfi/core/src/interfaces/delegator/INetworkRestakeDelegator.sol";
import {IBaseSlasher} from "@symbioticfi/core/src/interfaces/slasher/IBaseSlasher.sol";
import {IVetoSlasher} from "@symbioticfi/core/src/interfaces/slasher/IVetoSlasher.sol";
// ...
address VAULT_CONFIGURATOR = 0x0000000000000000000000000000000000000000; // address of the VaultConfigurator (see Deployments page)
// ...
address hook = <HOOK>;
address[] memory networkLimitSetRoleHolders = new address[](2);
networkLimitSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
networkLimitSetRoleHolders[1] = hook;
address[] memory operatorNetworkSharesSetRoleHolders = new address[](2);
operatorNetworkSharesSetRoleHolders[0] = 0xe8616DEcea16b5216e805B0b8caf7784de7570E7;
operatorNetworkSharesSetRoleHolders[1] = hook;
(address vault, address networkRestakeDelegator, address vetoSlasher) = IVaultConfigurator(VAULT_CONFIGURATOR).create(
IVaultConfigurator.InitParams({
version: 1, // Vault’s version (= common one)
owner: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s owner (can migrate the Vault to new versions in the future)
vaultParams: abi.encode(IVault.InitParams({
collateral: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, // address of the collateral - wstETH
burner: <BURNER_ROUTER>, // address of the deployed burner router
epochDuration: 604800, // duration of the Vault epoch in seconds (= 7 days)
depositWhitelist: false, // if enable deposit whitelisting
isDepositLimit: false, // if enable deposit limit
depositLimit: 0, // deposit limit
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Vault’s admin (can manage all roles)
depositWhitelistSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit whitelisting
depositorWhitelistRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the depositors whitelister
isDepositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the enabler/disabler of the deposit limit
depositLimitSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the deposit limit setter
})),
delegatorIndex: 0, // Delegator’s type (= NetworkRestakeDelegator)
delegatorParams: abi.encode(INetworkRestakeDelegator.InitParams({
baseParams: IBaseDelegator.BaseParams({
defaultAdminRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7, // address of the Delegator’s admin (can manage all roles)
hook: hook, // address of the hook (if not zero, receives onSlash() call on each slashing)
hookSetRoleHolder: 0xe8616DEcea16b5216e805B0b8caf7784de7570E7 // address of the hook setter
}),
networkLimitSetRoleHolders: networkLimitSetRoleHolders, // array of addresses of the network limit setters
operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders // array of addresses of the operator-network shares setters
})),
withSlasher: true, // if enable Slasher module
slasherIndex: 1, // Slasher’s type (= VetoSlasher)
slasherParams: abi.encode(IVetoSlasher.InitParams({
baseParams: IBaseSlasher.BaseParams({
isBurnerHook: true // if enable the `burner` to receive onSlash() call after each slashing (is needed for the burner router workflow)
}),
vetoDuration: 86400, // veto duration (= 1 day)
resolverSetEpochsDelay: 3 // number of Vault epochs needed for the resolver to be changed
}))
})
);
// ...
Configure core modules with hook after deployment
- We need the address of the Vault’s Delegator contract. It can be obtained via
Vault.delegator()
- The caller must have a
Delegator.HOOK_SET_ROLE()
role via the OpenZeppelin’sAccessControl
contract - Call
Delegator.setHook(hook)