Skip to content

Native Restaking

Ethereum native restaking on Karak utilizes a continual balance snapshot system combined with an ERC4626 vault, where the users are allocated share tokens to handle their stake. The users can restake the Ethereum staked on their Beacon Chain validators to Karak Operators, which serve as validators for the Karak Distributed Secure Services (DSS). Users will continue to earn yields from their beacon chain validators, which are also immediately restakable to the respective Karak Operators. Additionally, they will earn rewards from the Operators they choose to restake into proportional to the restaked amount.

Terminologies and Architecture

Karak Native Restaking builds upon the existing Karak Core standards, which enables easy deployment of the vault handling native restaking. The architecture comprises a NativeVault which is an ERC4626 vault with overridden functions to handle shares based on a virtual asset instead of an ERC20 token. The NativeVault is deployed and managed by Core and is always in a 1:1 mapping with an Operator. According to the Karak Core concepts this implies that a vault has been delegated to the Operator. A NativeVault consists of a createNode function which deploys a new NativeNode for the caller. A NativeNode is deployed at a CREATE2 address which takes in both the Operator and the function caller as the salt, hence enforcing a single NativeNode per user per operator. The details of a NativeNode is stored as an owner => NativeNode mapping in the NativeVault leaving the NativeNode as a simple holder contract with just a withdrawal function and NativeVault serving as the Beacon for the contract implementation. A NativeNode can support multiple validators by calling a NativeVault function to prove that the validator’s withdrawal credential is pointed to the owner’s NativeNode i.e. - validateWithdrawalCredentials. Hence, all the Ethereum yields coming from the validator will be accrued at the NativeNode and the owner will be awarded shares for it. The shares are then delegated to the Operator of the NativeVault and are subject to slashing. The share price mathematics remains the same as a standard ERC4626 vault. The NativeVault is initialized with an address where it is supposed to send all the slashed ETH called the slashStore. This is the common address to accumulate all slashed assets coming from different operators such that it is easy for the slashing handler to decide what to do with those assets. How the slashStore accumulates slashed ETH is discussed later in the document. With all the terminologies explained the following architecture diagram should be legible –

Native Restaking Architecture

Deploying a NativeNode

Deploy Flow

The NativeVault provides a function to deploy a new NativeNode using:

function `createNode`() external;

This deploys a new NativeNode with msg.sender as the owner of that node.

  1. The user then needs to set the address of the NativeNode contract as the withdrawal credentials for the validators they wish to restake.
  2. Consequently, the user has to register Beacon Chain validators into it to be awarded shares for their staked ETH.

For that the node owner needs to call the following function in the INativeVault interface:

function `validateWithdrawalCredentials`(
    BeaconProofs.BeaconStateRootProof calldata beaconStateRootProof,
    BeaconProofs.ValidatorFieldsProof[] calldata validatorFieldProofs
) external;

The proofs can be generated using the Karak CLI. Once successful, this function will mark these validators as ACTIVE and increase the activeValidatorCount for the node.

Creating Snapshots

Karak native restaking utilizes a balance snapshot system to keep the NativeNode aware of the balance changes on the beacon chain. NativeVault updates the node owner's shares based on the balance changes on their NativeNode and the beacon chain balance of the validators, which in turn changes the amount the node owner has staked into an Operator, affecting the rewards given to them. A user can create multiple native nodes staked into different Karak Operators by calling the createNode function in the NativeVault that is delegated to the Operator they would like to stake into. They have to handle all their native nodes individually by submitting periodic snapshots and can withdraw ETH from each node independently.

In a scenario where a native node’s snapshot has not been updated for a given time period (currently 7 days) the last snapshot is marked expired. In this case, anyone can start the snapshot on behalf of the owner.

The snapshot (balance update) merkle proof can be generated using the Karak CLI. The following flow diagram should capture the basic snapshot flow:

Snapshot Flow

The following steps need to be performed to finish a new snapshot:

  1. Start a new snapshot using this function – function startSnapshot() external. If there is no balance change from the last snapshot state, then the function will revert with a NoBalanceUpdateToSnapshot error.
  2. Submit snapshot (balance) proofs for all active validators in the NativeNode using this function –
function validateSnapshotProofs(
    address nodeOwner,
    BeaconProofs.BalanceProof[] calldata balanceProofs,
    BeaconProofs.BalanceContainer calldata balanceContainer
) external;
  1. Once a snapshot proof has been submitted, the snapshot will automatically be finalized and emit a SnapshotFinished event.

The native vault's totalAssets is a uint256 which is updated based on this equation – Asset Math Based on this the equivalent shares are awarded to the node owner. This process can be outsourced to a cron job or keeper service to regularly update restaked ETH.

Withdrawing from the NativeNode

The validators registered with a NativeNode have their withdrawal credentials pointed to the respective nodes. Therefore, all the partial withdrawals coming from the beacon chain are accrued at the NativeNode.

Node owners have to go through a withdrawal queue to withdraw their accumulated ETH from their NativeNode. This withdrawal queue is meant to prevent a bad actor from indulging in slashable behavior and immediately withdrawing their funds. After starting a withdrawal the node owner is allowed to finish the withdrawal only after the MIN_WITHDRAW_DELAY has passed to prevent escaping slashable behavior. The withdrawal can be started and finished using these functions in the NativeVault:

function startWithdrawal(address to, uint256 weiAmount) external returns (bytes32 withdrawalKey);
 
function finishWithdrawal(bytes32 withdrawalKey) external;

However, not all ETH accumulated at the NativeNode is withdrawable by the node owner since it depends on the share token value of the node owner. We will look at how slashing works in the following sections.

Full Withdrawals

In case a user chooses to withdraw their validator fully, all the ETH coming to the NativeNode can be withdrawn using the same function as above. When the next snapshot is submitted and the beacon balance is 0 the validator status in the NativeNode is marked as WITHDRAWN. Consequently, that validator does not need a snapshot proof to be submitted for finishing snapshots.

Slashing and SlashStore

When an operator that has delegated native staked ETH is slashed, the totalAssets in the NativeVault is decreased by the slashing amount (emulating assets being transferred out of the vault to the slashingHandler). This emulation behavior is necessary since at the point of slashing, all the slashed ETH need not be available at the nodes. Additionally, transferring slashed assets from all nodes will turn out to be an O(n) operation.

Instead, whenever a new snapshot is started, the slashed assets are calculated using this equation –

Slashed Asset Calculation

This is still not all the available assets that can be withdrawn at the time, hence the sweepable ETH is calculated as – min(node.balance, slashedAssets)

Then, the sweepable ETH is withdrawn to the slashStore from where it is up to the slashing handler on what should be done with the slashed ETH.

Slashing Flow