Events
This page provides a complete overview of event handling and math across the Symbiotic system.
Vault Stake
Consider the following events monitoring only a single vault.
Deposit
event Deposit(address indexed depositor, address indexed onBehalfOf, uint256 amount, uint256 shares)depositor- irrelevant in the current contextonBehalfOf- an address of the user that receives the deposited fundsamount- a number ofcollateraltokens depositedshares- a number of sharesonBehalfOfuser received
Bucket accounting (why amount and shares coexist)
- Alice deposits 12 →
userShares(alice)=12,totalAssets=12,totalShares=12→ balance = 12. - Bob deposits 8 →
userShares(bob)=8,totalAssets=20,totalShares=20→ balance = 8. - Slash 10 →
totalAssets=10→ balances: Alice 6, Bob 4. - Jack deposits 10 → minted shares =
depositAmount * totalShares / totalAssets=10 * 20 / 10 = 20→userShares(jack)=20,totalAssets=20,totalShares=40→ balances: Alice 6, Bob 4, Jack 10.
Off-chain updates per Deposit
activeStakeBucket.userShares(onBehalfOf) += sharesactiveStakeBucket.totalAssets += amountactiveStakeBucket.totalShares += shares
Withdraw
event Withdraw(address indexed withdrawer, address indexed claimer, uint256 amount, uint256 burnedShares, uint256 mintedShares)withdrawer- an address that loses the withdrawn fundsclaimer- an address that receives withdrawn funds to be claimedamount- a number ofcollateraltokens withdrawnburnedShares- a number of shareswithdrawerlost at theactiveStakeBucketmintedShares- a number of sharesclaimerreceived
Symbiotic Vault works using the epochs mechanic. What does it mean in the context of withdrawals:
Let’s assume the epoch duration of the Vault is 7 days, and the 0th epoch started at timestamp = 0. Right now, it is a timestamp = 0.
- Alice deposited 10 (
activeStakeBucket's state has changed following the logic mentioned above)
Timestamp = 3 days (the 0th epoch)
- Alice withdraws 3, which means:
- Decrease
activeStakeBucket.userShares(alice)by 3 (withdrawal logic from the bucket is similar to a deposit). - Decrease
activeStakeBucket.totalAssetsby 3 (withdrawal logic from the bucket is similar to a deposit). - Decrease
activeStakeBucket.totalSharesby 3 (withdrawal logic from the bucket is similar to a deposit). - Deposit 3 into
withdrawalsBucket_1(1means it is a bucket related to the 1st epoch; “the 1st epoch” because it is the next one)
- Decrease
Timestamp = 10 days (the 1st epoch)
- Alice withdraws 4, which means:
- Withdraw 4 from
activeStakeBucket - Deposit 4 to
withdrawalsBucket_2
- Withdraw 4 from
Off-chain updates per Withdraw
activeStakeBucket.userShares(withdrawer) -= burnedSharesactiveStakeBucket.totalAssets -= amountactiveStakeBucket.totalShares -= burnedShareswithdrawalsBucket_i.userShares(claimer) += mintedShareswithdrawalsBucket_i.totalAssets += amountwithdrawalsBucket_i.totalShares += mintedShares
Slash
event OnSlash(uint256 amount, uint48 captureTimestamp, uint256 slashedAmount)amount- a number ofcollateraltokens that were requested to be slashedcaptureTimestamp- a capture timestamp the slash was requested forslashedAmount- a number ofcollateraltokens that were actually slashed
Basically, the logic behind the slashing is the following:
- A network receives the stake amount only depending on the active stake at the given time point.
- Also, we know that the withdrawals from the current epoch are marked as
withdrawalsBucket_i, whereiis the next epoch. - Therefore, omitting some details, the slashing can be divided into 2 cases:
- If
eventTimestampandcaptureTimestampare in different epochs (it can be simply checked viaVault.epochAt(timestamp)function).- Let’s say
eventEpoch=Vault.epochAt(eventTimestamp) - Then the whole slashable amount for this slashing
slashableAmount=activeStakeBucket.totalAssets+withdrawalsBucket_{eventEpoch}.totalAssets+withdrawalsBucket_{eventEpoch + 1}.totalAssets - The
slashedAmountis proportionally decreased from each bucket, e.g.,activeStakeSlashed=slashedAmount*activeStakeBucket.totalAssets/slashableAmount
- Let’s say
- If
eventTimestampandcaptureTimestampare in the same epoch (it can be simply checked viaVault.epochAt(timestamp)function).- Let’s say
eventEpoch=Vault.epochAt(eventTimestamp) - Then the whole slashable amount for this slashing
slashableAmount=activeStakeBucket.totalAssets+withdrawalsBucket_{eventEpoch + 1}.totalAssets - The
slashedAmountis proportionally decreased from each bucket, e.g.,activeStakeSlashed=slashedAmount*activeStakeBucket.totalAssets/slashableAmount
- Let’s say
- If

Slashing Logic - click to get more details
Off-chain updates per OnSlash
Let eventEpoch = Vault.epochAt(eventTimestamp).
-
If
eventTimestampandcaptureTimestampare in different epochs:slashableAmount = activeStakeBucket.totalAssets + withdrawalsBucket_{eventEpoch}.totalAssets + withdrawalsBucket_{eventEpoch + 1}.totalAssetsactiveSlashed = slashedAmount * activeStakeBucket.totalAssets / slashableAmountnextWithdrawalsSlashed = slashedAmount * withdrawalsBucket_{eventEpoch + 1}.totalAssets / slashableAmountwithdrawalsSlashed = slashedAmount - activeStakeSlash - withdrawalsNext- If
withdrawalsBucket_{eventEpoch}.totalAssets < withdrawalsSlashed:nextWithdrawalsSlashed += withdrawalsSlashed - withdrawalsBucket_{eventEpoch}.totalAssetswithdrawalsSlashed = withdrawalsBucket_{eventEpoch}.totalAssets
activeStakeBucket.totalAssets -= activeSlashedwithdrawalsBucket_{eventEpoch + 1}.totalAssets -= nextWithdrawalsSlashedwithdrawalsBucket_{eventEpoch}.totalAssets -= withdrawalsSlashed
-
If
eventTimestampandcaptureTimestampare in the same epoch:slashableAmount = activeStakeBucket.totalAssets + withdrawalsBucket_{eventEpoch + 1}.totalAssetsactiveSlashed = slashedAmount * activeStakeBucket.totalAssets / slashableAmountnextWithdrawalsSlashed = slashedAmount - activeSlashedactiveStakeBucket.totalAssets -= activeSlashedwithdrawalsBucket_{eventEpoch + 1}.totalAssets -= nextWithdrawalsSlashed
Transfer
event Transfer(address indexed from, address indexed to, uint256 value)from- an address of the senderto- an address of the recipientvalue- a number of tokens transferred From the start, we provide two versions of the Vault:
- A common Vault that contains only events described above
- A tokenized Vault, which represents active stake shares as ERC20 tokens and, therefore, adds a
Transferevent There are 3 possible cases in the sense of theTransferevent: fromis zero, andtois not zero - it means minting the tokenfromis not zero, andtois zero - it means burning the tokenfromis not zero, andtois not zero - it means a transfer of the token In our case, the token's minting and burning are already calculated viaDepositandWithdrawevents. Therefore, only transfers need off-chain handling:
- If
from≠0x0000000000000000000000000000000000000000andto≠0x0000000000000000000000000000000000000000activeStakeBucket.userShares(from) -= valueactiveStakeBucket.userShares(to) += value
Formulas
Now, given we have all the data across all the stakers in the vault, we can get some Vault stake-related data:
Registries
OperatorRegistry
Monitor events using OperatorRegistry address
event AddEntity(address indexed entity)entity- an address of the registered operator
Add entity to the allOperators list
NetworkRegistry
Monitor events using NetworkRegistry address
event AddEntity(address indexed entity)entity- an address of the registered network
Add entity to the allNetworks list
VaultFactory
Monitor events using VaultFactory address
event AddEntity(address indexed entity)entity- an address of the created Vault
Add entity to the allVaults list
Delegations
Operator-Network Opt-in
Monitor events using NetworkOptinService address
event OptIn(address indexed who, address indexed where)who- an address of the operator who opted intowhere- an address of the network where the operator opted into
event OptOut(address indexed who, address indexed where)who- an address of the operator who opted outwhere- an address of the network where the operator opted out from
On each of these events, it is needed to save if the operator is opted into the network like:
- If
OptInoperatorNetworkOptIn(operator, network) = true
- if
OptOutoperatorNetworkOptIn(operator, network) = false
Operator-Vault Opt-in
Monitor events using VaultOptinService address
event OptIn(address indexed who, address indexed where)who- an address of the operator who opted intowhere- an address of the vault where the operator opted into
event OptOut(address indexed who, address indexed where)who- an address of the operator who opted outwhere- an address of the vault where the operator opted out from
On each of these events, it is needed to save if the operator is opted into the vault like:
- If
OptInoperatorVaultOptIn(operator, network) = true
- if
OptOutoperatorVaultOptIn(operator, network) = false
MaxNetworkLimit
Monitor events using Vault’s Delegator address
event SetMaxNetworkLimit(bytes32 indexed subnetwork, uint256 amount)subnetwork- a full identifier of the subnetwork (the first 40 bytes is an address of thenetwork, the last 24 bytes are theidentifier)amount- a maximum network limit
Limits’ setting may perform differently depending on the Delegator’s type:
- if
NetworkRestakeDelegator(type 0)maxNetworkLimit(network, identifier) = amountnetworkLimit(network, identifier) = min(networkLimit(network, identifier), amount)
- if
FullRestakeDelegator(type 1)maxNetworkLimit(network, identifier) = amountnetworkLimit(network, identifier) = min(networkLimit(network, identifier), amount)
- if
FullRestakeDelegator(type 2)maxNetworkLimit(network, identifier) = amountnetworkLimit(network, identifier) = min(networkLimit(network, identifier), amount)
NetworkLimit (if Delegator’s type in [0, 1, 2])
Monitor events using Vault’s Delegator address
event SetNetworkLimit(bytes32 indexed subnetwork, uint256 amount)subnetwork- a full identifier of the subnetwork (the first 40 bytes is an address of thenetwork, the last 24 bytes are theidentifier)amount- a network limit
Limits’ setting may perform differently depending on the Delegator’s type:
- if
NetworkRestakeDelegator(type 0)networkLimit(network, identifier) = amount
- if
FullRestakeDelegator(type 1)networkLimit(network, identifier) = amount
- if
FullRestakeDelegator(type 2)networkLimit(network, identifier) = amount
OperatorNetworkShares (if Delegator’s type in [0])
Monitor events using Vault’s Delegator address
event SetOperatorNetworkShares(bytes32 indexed subnetwork, address indexed operator, uint256 shares)subnetwork- a full identifier of the subnetwork (the first 40 bytes is an address of thenetwork, the last 24 bytes are theidentifier)operator- an address of the operator to set shares forshares- a number of shares to set
Limits’ setting may perform differently depending on the Delegator’s type:
- if
NetworkRestakeDelegator(type 0)totalOperatorNetworkShares(network, identifier) = totalOperatorNetworkShares(network, identifier) - operatorNetworkShares(network, identifier, operator) + sharesoperatorNetworkShares(network, identifier, operator) = shares
OperatorNetworkLimit (if Delegator’s type in [1])
Monitor events using Vault’s Delegator address
event SetOperatorNetworkLimit(bytes32 indexed subnetwork, address indexed operator, uint256 amount)subnetwork- a full identifier of the subnetwork (the first 40 bytes is an address of thenetwork, the last 24 bytes are theidentifier)operator- an address of the operator to set limit foramount- a limit to set
Limits’ setting may perform differently depending on the Delegator’s type:
- if
FullRestakeDelegator(type 1)operatorNetworkLimit(network, identifier, operator) = amount
