Skip to main content

Accounting

Accounting.sol is a supplementary contract responsible for managing Node Operators' bond, rewards, penalties, and charges. It stores the bond as stETH shares and tracks the required bond using configurable bond curves associated with each Node Operator type. The contract also manages bond locks, records bond debt when the bond is insufficient to cover penalties, and integrates with the FeeDistributor to pull and split staking fee rewards.

A Node Operator can perform a number of operations directly through this contract, for example:

  • top up the bond in ETH, stETH, or wstETH;
  • claim rewards and/or excess bond as stETH, wstETH, or a withdrawal NFT (unstETH);
  • pull staking fee rewards into the bond and split them according to the configured fee splits;
  • configure reward fee splits;
  • set a custom rewards claimer to claim on the operator's behalf;
  • unlock an expired bond lock.

Upgradability​

The contract uses OssifiableProxy for upgradability.

State Variables​

INITIALIZED_VERSION​

uint64 internal constant INITIALIZED_VERSION = 3

MANAGE_BOND_CURVES_ROLE​

bytes32 public constant MANAGE_BOND_CURVES_ROLE = keccak256("MANAGE_BOND_CURVES_ROLE")

SET_BOND_CURVE_ROLE​

bytes32 public constant SET_BOND_CURVE_ROLE = keccak256("SET_BOND_CURVE_ROLE")

MODULE​

IBaseModule public immutable MODULE

FEE_DISTRIBUTOR​

IFeeDistributor public immutable FEE_DISTRIBUTOR

_rewardsClaimers​

mapping(uint256 nodeOperatorId => address rewardsClaimer) internal _rewardsClaimers

chargePenaltyRecipient​

address public chargePenaltyRecipient

Functions​

onlyModule​

modifier onlyModule() ;

constructor​

constructor(
address lidoLocator,
address module,
address feeDistributor,
uint256 minBondLockPeriod,
uint256 maxBondLockPeriod
) BondCore(lidoLocator) BondLock(minBondLockPeriod, maxBondLockPeriod);

Parameters

NameTypeDescription
lidoLocatoraddressLido Locator contract address
moduleaddressStaking Module contract address
feeDistributoraddressFee Distributor contract address
minBondLockPerioduint256Min time in seconds for the bondLock period
maxBondLockPerioduint256Max time in seconds for the bondLock period

initialize​

Initialize contract from scratch. In case of a method call frontrun, the contract instance should be discarded. It is recommended to call this method in the same transaction as the deployment transaction and perform extensive deployment verification before using the contract instance.

function initialize(
BondCurveIntervalInput[] calldata bondCurve,
address admin,
uint256 bondLockPeriod,
address _chargePenaltyRecipient
) external reinitializer(INITIALIZED_VERSION);

Parameters

NameTypeDescription
bondCurveBondCurveIntervalInput[]Initial bond curve
adminaddressAdmin role member address
bondLockPerioduint256Bond lock period in seconds
_chargePenaltyRecipientaddressRecipient of the charge penalty type

finalizeUpgradeV3​

This method is expected to be called only when the contract is upgraded from version 2 to version 3 for the existing version 2 deployment. If the version 3 contract is deployed from scratch, the initialize method should be used instead. To prevent possible frontrun this method should strictly be called in the same TX as the upgrade transaction and should not be called separately.

function finalizeUpgradeV3() external reinitializer(INITIALIZED_VERSION);

setChargePenaltyRecipient​

Set charge recipient address

function setChargePenaltyRecipient(address _chargePenaltyRecipient) external onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
_chargePenaltyRecipientaddressCharge recipient address

setBondLockPeriod​

Set bond lock period

function setBondLockPeriod(uint256 period) external onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
perioduint256Period in seconds to retain bond lock

updateFeeSplits​

Set fee splits for the given Node Operator

FeeSplits can be updated either when there are no splits currently or when there are splits now, provided all node operator rewards are distributed and split. It is possible to set splits while there are undistributed node operator rewards and no splits are currently set. This will result in all undistributed node operator rewards being split. If a node operator has never received any node operator rewards, they can set initial splits. However, further change will be possible only after getting and splitting the first rewards.

function updateFeeSplits(
uint256 nodeOperatorId,
FeeSplit[] calldata feeSplits,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external;

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
feeSplitsFeeSplit[]Array of FeeSplit structs defining recipients and their shares in basis points Total shares must be <= 10_000 (100%). Remainder goes to the Node Operator's bond
cumulativeFeeSharesuint256Cumulative fee stETH shares for the Node Operator. Optional
rewardsProofbytes32[]Merkle proof of the rewards. Optional

addBondCurve​

Add a new bond curve

function addBondCurve(BondCurveIntervalInput[] calldata bondCurve)
external
onlyRole(MANAGE_BOND_CURVES_ROLE)
returns (uint256 id);

Parameters

NameTypeDescription
bondCurveBondCurveIntervalInput[]Bond curve definition to add

Returns

NameTypeDescription
iduint256Id of the added curve

updateBondCurve​

Update existing bond curve

If the curve is updated to a curve with higher values for any point, extensive checks and actions should be performed by the method caller to avoid inconsistency in the keys accounting. A manual update of the depositable validators count in staking module might be required to ensure that the keys pointers are consistent. Note that node operators might face unbonded keys due to changes to bond requirements.

function updateBondCurve(uint256 curveId, BondCurveIntervalInput[] calldata bondCurve)
external
onlyRole(MANAGE_BOND_CURVES_ROLE);

Parameters

NameTypeDescription
curveIduint256Bond curve ID to update
bondCurveBondCurveIntervalInput[]Bond curve definition

setBondCurve​

Set the bond curve for the given Node Operator

Updates depositable validators count in staking module to ensure key pointers consistency

function setBondCurve(uint256 nodeOperatorId, uint256 curveId) external onlyRole(SET_BOND_CURVE_ROLE);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
curveIduint256ID of the bond curve to set

depositETH​

Stake user's ETH with Lido and deposit stETH to the bond

Called by staking module exclusively. Staking module should check node operator existence and update depositable validators count

function depositETH(address from, uint256 nodeOperatorId) external payable whenResumed onlyModule;

Parameters

NameTypeDescription
fromaddressAddress to stake ETH and deposit stETH from
nodeOperatorIduint256ID of the Node Operator

depositETH​

Stake user's ETH with Lido and deposit stETH to the bond

Permissionless. Enqueues Node Operator's keys if needed

function depositETH(uint256 nodeOperatorId) external payable whenResumed;

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator

depositStETH​

Deposit user's stETH to the bond for the given Node Operator

Called by staking module exclusively. Staking module should check node operator existence and update depositable validators count

function depositStETH(address from, uint256 nodeOperatorId, uint256 stETHAmount, PermitInput calldata permit)
external
whenResumed
onlyModule;

Parameters

NameTypeDescription
fromaddressAddress to deposit stETH from.
nodeOperatorIduint256ID of the Node Operator
stETHAmountuint256Amount of stETH to deposit
permitPermitInputstETH permit for the contract

depositStETH​

Deposit user's stETH to the bond for the given Node Operator

Permissionless. Enqueues Node Operator's keys if needed

function depositStETH(uint256 nodeOperatorId, uint256 stETHAmount, PermitInput calldata permit)
external
whenResumed;

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
stETHAmountuint256Amount of stETH to deposit
permitPermitInputstETH permit for the contract

depositWstETH​

Unwrap the user's wstETH and deposit stETH to the bond for the given Node Operator

Called by staking module exclusively. Staking module should check node operator existence and update depositable validators count

function depositWstETH(address from, uint256 nodeOperatorId, uint256 wstETHAmount, PermitInput calldata permit)
external
whenResumed
onlyModule;

Parameters

NameTypeDescription
fromaddressAddress to unwrap wstETH from
nodeOperatorIduint256ID of the Node Operator
wstETHAmountuint256Amount of wstETH to deposit
permitPermitInputwstETH permit for the contract

depositWstETH​

Unwrap the user's wstETH and deposit stETH to the bond for the given Node Operator

Permissionless. Enqueues Node Operator's keys if needed

function depositWstETH(uint256 nodeOperatorId, uint256 wstETHAmount, PermitInput calldata permit)
external
whenResumed;

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
wstETHAmountuint256Amount of wstETH to deposit
permitPermitInputwstETH permit for the contract

claimRewardsStETH​

Claim full reward (fee + bond) in stETH for the given Node Operator with desirable value. rewardsProof and cumulativeFeeShares might be empty in order to claim only excess bond

It's impossible to use single-leaf proof via this method, so this case should be treated carefully by off-chain tooling, e.g. to make sure a tree has at least 2 leaves.

function claimRewardsStETH(
uint256 nodeOperatorId,
uint256 stETHAmount,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external whenResumed returns (uint256 claimedShares);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
stETHAmountuint256Amount of stETH to claim
cumulativeFeeSharesuint256Cumulative fee stETH shares for the Node Operator
rewardsProofbytes32[]Merkle proof of the rewards

Returns

NameTypeDescription
claimedSharesuint256shares Amount of stETH shares claimed

claimRewardsWstETH​

Claim full reward (fee + bond) in wstETH for the given Node Operator available for this moment. rewardsProof and cumulativeFeeShares might be empty in order to claim only excess bond

It's impossible to use single-leaf proof via this method, so this case should be treated carefully by off-chain tooling, e.g. to make sure a tree has at least 2 leaves.

function claimRewardsWstETH(
uint256 nodeOperatorId,
uint256 wstETHAmount,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external whenResumed returns (uint256 claimedWstETH);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
wstETHAmountuint256Amount of wstETH to claim
cumulativeFeeSharesuint256Cumulative fee stETH shares for the Node Operator
rewardsProofbytes32[]Merkle proof of the rewards

Returns

NameTypeDescription
claimedWstETHuint256claimedWstETHAmount Amount of wstETH claimed

claimRewardsUnstETH​

Request full reward (fee + bond) in Withdrawal NFT (unstETH) for the given Node Operator available for this moment. rewardsProof and cumulativeFeeShares might be empty in order to claim only excess bond

Reverts if amount isn't between MIN_STETH_WITHDRAWAL_AMOUNT and MAX_STETH_WITHDRAWAL_AMOUNT

function claimRewardsUnstETH(
uint256 nodeOperatorId,
uint256 stETHAmount,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external whenResumed returns (uint256 requestId);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
stETHAmountuint256Amount of stETH to request
cumulativeFeeSharesuint256Cumulative fee stETH shares for the Node Operator
rewardsProofbytes32[]Merkle proof of the rewards

Returns

NameTypeDescription
requestIduint256Withdrawal NFT ID

lockBond​

Lock bond in ETH for the given Node Operator

Called by staking module exclusively

function lockBond(uint256 nodeOperatorId, uint256 amount) external onlyModule;

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
amountuint256Amount to lock in ETH (stETH)

releaseLockedBond​

Release locked bond in ETH for the given Node Operator

Called by staking module exclusively

function releaseLockedBond(uint256 nodeOperatorId, uint256 amount) external onlyModule returns (bool released);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
amountuint256Amount to release in ETH (stETH)

Returns

NameTypeDescription
releasedboolTrue if the bond was released, false if the lock was expired and bond was unlocked instead

unlockExpiredLock​

Unlock expired locked bond for the given Node Operator

function unlockExpiredLock(uint256 nodeOperatorId) public;

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator

compensateLockedBond​

Compensate locked bond ETH for the given Node Operator

Called by staking module exclusively

function compensateLockedBond(uint256 nodeOperatorId) external onlyModule returns (uint256 compensatedAmount);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator

Returns

NameTypeDescription
compensatedAmountuint256Amount compensated in ETH (stETH)

settleLockedBond​

Settle locked bond ETH for the given Node Operator

Called by staking module exclusively

function settleLockedBond(uint256 nodeOperatorId, uint256 bondLockNonce) external onlyModule returns (uint256);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
bondLockNonceuint256Bond lock nonce

Returns

NameTypeDescription
<none>uint256amountSettled Amount settled in ETH (stETH)

penalize​

Penalize bond by burning stETH shares of the given Node Operator

Penalty application has a priority over the locked bond. Method call can result in the remaining bond being lower than the locked bond.

function penalize(uint256 nodeOperatorId, uint256 amount) external onlyModule returns (bool penaltyCovered);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
amountuint256Amount to penalize in ETH (stETH)

Returns

NameTypeDescription
penaltyCoveredboolTrue if the penalty was fully covered by bond burn, false otherwise

chargeFee​

Charge fee from bond by transferring stETH shares of the given Node Operator to the charge recipient

Charge confiscation has a priority over the locked bond. Method call can result in the remaining bond being lower than the locked bond.

function chargeFee(uint256 nodeOperatorId, uint256 amount) external onlyModule returns (bool);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
amountuint256Amount to charge in ETH (stETH)

Returns

NameTypeDescription
<none>boolWhether any shares were actually transferred

pullAndSplitFeeRewards​

Pull fees (if proof provided) from FeeDistributor to the Node Operator's bond and split according to configured fee splits.

Reverts while Accounting is paused. Previously reward pulling during pause was useful for emergency penalty handling. Now penalties can create bond debt while paused, and later rewards can repay it after resume. So this method is paused together with the rest of the reward-handling flows.

function pullAndSplitFeeRewards(
uint256 nodeOperatorId,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external whenResumed;

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
cumulativeFeeSharesuint256Cumulative fee stETH shares for the Node Operator
rewardsProofbytes32[]Merkle proof of the rewards

setCustomRewardsClaimer​

Set custom rewards claimer for the given Node Operator. This address will be able to claim rewards on behalf of the Node Operator. The rewards will be transferred to the Node Operator's reward address as usual.

function setCustomRewardsClaimer(uint256 nodeOperatorId, address rewardsClaimer) external;

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
rewardsClaimeraddressAddress allowed to claim rewards on behalf of the Node Operator

recoverERC20​

Allows sender to recover ERC20 tokens held by the contract

function recoverERC20(address token, uint256 amount) external override;

Parameters

NameTypeDescription
tokenaddressThe address of the ERC20 token to recover
amountuint256The amount of the ERC20 token to recover

recoverStETHShares​

Recover all stETH shares from the contract

Accounts for the bond funds stored during recovery

function recoverStETHShares() external;

getInitializedVersion​

Get the initialized version of the contract

function getInitializedVersion() external view returns (uint64);

getCustomRewardsClaimer​

Get the custom rewards claimer for the given Node Operator. This address is allowed to claim rewards on behalf of the Node Operator. The rewards are still transferred to the Node Operator's reward address as usual.

function getCustomRewardsClaimer(uint256 nodeOperatorId) external view returns (address);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator

Returns

NameTypeDescription
<none>addressAddress allowed to claim rewards on behalf of the Node Operator

getUnbondedKeysCount​

Get the number of the unbonded keys

function getUnbondedKeysCount(uint256 nodeOperatorId) external view returns (uint256);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator

Returns

NameTypeDescription
<none>uint256Unbonded keys count

getUnbondedKeysCountToEject​

Get the number of the unbonded keys to be ejected using a forcedTargetLimit Locked bond is not considered for this calculation to allow Node Operators to compensate the locked bond via compensateLockedBond method before the ejection happens

function getUnbondedKeysCountToEject(uint256 nodeOperatorId) external view returns (uint256);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator

Returns

NameTypeDescription
<none>uint256Unbonded keys count

getBondAmountByKeysCountWstETH​

Get the bond amount in wstETH required for the keysCount keys for the given bond curve

function getBondAmountByKeysCountWstETH(uint256 keysCount, uint256 curveId) external view returns (uint256);

Parameters

NameTypeDescription
keysCountuint256Keys count to calculate the required bond amount
curveIduint256Id of the curve to perform calculations against

Returns

NameTypeDescription
<none>uint256wstETH amount required for the keysCount

getRequiredBondForNextKeysWstETH​

Get the required bond in wstETH (inc. missed and excess) for the given Node Operator to upload new keys

function getRequiredBondForNextKeysWstETH(uint256 nodeOperatorId, uint256 additionalKeys)
external
view
returns (uint256);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
additionalKeysuint256Number of new keys to add

Returns

NameTypeDescription
<none>uint256Required bond in wstETH

getClaimableBondShares​

Get current claimable bond in stETH shares for the given Node Operator

function getClaimableBondShares(uint256 nodeOperatorId) external view returns (uint256);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator

Returns

NameTypeDescription
<none>uint256Current claimable bond in stETH shares

getClaimableRewardsAndBondShares​

Get current claimable bond in stETH shares for the given Node Operator Includes potential rewards distributed by the Fee Distributor

function getClaimableRewardsAndBondShares(
uint256 nodeOperatorId,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external view returns (uint256 claimableShares);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
cumulativeFeeSharesuint256Cumulative fee stETH shares for the Node Operator
rewardsProofbytes32[]Merkle proof of the rewards

Returns

NameTypeDescription
claimableSharesuint256Current claimable bond in stETH shares

getNodeOperatorBondInfo​

Get all bond-related info for the given Node Operator in one call

function getNodeOperatorBondInfo(uint256 nodeOperatorId) external view returns (NodeOperatorBondInfo memory info);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator

Returns

NameTypeDescription
infoNodeOperatorBondInfoBond info containing current bond, required bond, locked bond, bond debt, and pending shares to split

getBondSummary​

Get current and required bond amounts in ETH (stETH) for the given Node Operator

To calculate excess bond amount subtract required from current value. To calculate missed bond amount subtract current from required value

function getBondSummary(uint256 nodeOperatorId) public view returns (uint256 current, uint256 required);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator

Returns

NameTypeDescription
currentuint256Current bond amount in ETH
requireduint256Required bond amount in ETH

getBondSummaryShares​

Get current and required bond amounts in stETH shares for the given Node Operator

To calculate excess bond amount subtract required from current value. To calculate missed bond amount subtract current from required value

function getBondSummaryShares(uint256 nodeOperatorId) public view returns (uint256 current, uint256 required);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator

Returns

NameTypeDescription
currentuint256Current bond amount in stETH shares
requireduint256Required bond amount in stETH shares

getRequiredBondForNextKeys​

Get the required bond in ETH (inc. missed and excess) for the given Node Operator to upload new deposit data

function getRequiredBondForNextKeys(uint256 nodeOperatorId, uint256 additionalKeys) public view returns (uint256);

Parameters

NameTypeDescription
nodeOperatorIduint256ID of the Node Operator
additionalKeysuint256Number of new keys to add

Returns

NameTypeDescription
<none>uint256Required bond amount in ETH

_pullAndSplitFeeRewards​

function _pullAndSplitFeeRewards(
uint256 nodeOperatorId,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) internal returns (uint256 claimableShares);

_unwrapPermitIfRequired​

function _unwrapPermitIfRequired(address token, address from, PermitInput calldata permit) internal;

_getClaimableBondShares​

Calculates claimable bond shares accounting for locked bond and withdrawn validators. Does not subtract pending split transfers, so in rare cases (e.g. locked bond or bond debt) may overestimate the operator-receivable amount. Off-chain integrations should account for getPendingSharesToSplit.

function _getClaimableBondShares(uint256 nodeOperatorId) internal view returns (uint256);

_getRequiredBond​

function _getRequiredBond(uint256 nodeOperatorId, uint256 additionalKeys) internal view returns (uint256);

_getRequiredBondShares​

function _getRequiredBondShares(uint256 nodeOperatorId, uint256 additionalKeys) internal view returns (uint256);

_getUnbondedKeysCount​

Unbonded stands for the amount of keys not fully covered with bond

function _getUnbondedKeysCount(uint256 nodeOperatorId, bool includeLockedBond) internal view returns (uint256);

_onlyRecoverer​

function _onlyRecoverer() internal view override;

__checkRole​

function __checkRole(bytes32 role) internal view override;

_onlyExistingNodeOperator​

function _onlyExistingNodeOperator(uint256 nodeOperatorId) internal view;

_onlyNodeOperatorOwner​

function _onlyNodeOperatorOwner(uint256 nodeOperatorId) internal view;

_onlyModule​

function _onlyModule() internal view;

_checkAndGetEligibleNodeOperatorProperties​

function _checkAndGetEligibleNodeOperatorProperties(uint256 nodeOperatorId)
internal
view
returns (NodeOperatorManagementProperties memory no);

_setChargePenaltyRecipient​

function _setChargePenaltyRecipient(address _chargePenaltyRecipient) private;