InfraredBERAWithdrawor
Inherits: Upgradeable, IInfraredBERAWithdrawor
Manages withdrawal requests for BERA tokens from the consensus layer in the Infrared liquid staking protocol.
Assumes BERA is returned via the EIP-7002 withdrawal precompile and credited to the contract. Inherits from Upgradeable
for governance and upgradability.
Tickets start at requestId = 1
, and requestLength
is incremented before assignment.
State Variables
WITHDRAW_PRECOMPILE
The address of the Withdraw Precompile settable in the next upgrade.
address public WITHDRAW_PRECOMPILE;
InfraredBERA
The address of the InfraredBERA.sol
contract.
address public InfraredBERA;
requestLength
The current number of withdrawal requests queued (next requestId
to be assigned).
uint256 public requestLength;
requests
Mapping of request IDs to withdrawal request tickets.
Key is the requestId
(starting at 1), and value is the WithdrawalRequest
struct.
mapping(uint256 => WithdrawalRequest) public requests;
requestsFinalisedUntil
The highest requestId
that has been finalized by process
.
uint256 public requestsFinalisedUntil;
totalClaimable
Total amount of BERA (in wei) marked as claimable (in PROCESSED
state, non-depositor tickets).
uint256 public totalClaimable;
minActivationBalance
Minimum balance for a validator to stay active.
uint256 public minActivationBalance;
pendingWithdrawalsLength
Length of pending withdrawals queue
uint256 public pendingWithdrawalsLength;
nonceProcess
holding slot for withdraworLite storage var
uint256 public nonceProcess;
pendingWithdrawals
Mapping to track individual pending withdrawal amounts
mapping(uint256 => PendingWithdrawal) public pendingWithdrawals;
__gap
Reserve storage slots for future upgrades for safety
uint256[39] private __gap;
Functions
initializeV2
Initializes the contract for the next version, setting the withdrawal precompile address.
Only callable by the governance role (onlyGovernor
). Reverts if _withdraw_precompile
is zero.
function initializeV2(address _withdraw_precompile) external onlyGovernor;
Parameters
Name | Type | Description |
---|---|---|
_withdraw_precompile | address | The address of the EIP-7002 withdrawal precompile. |
reserves
Returns the amount of BERA available for new withdrawals (excluding claimable funds).
function reserves() public view returns (uint256);
Returns
Name | Type | Description |
---|---|---|
<none> | uint256 | The contract’s balance minus totalClaimable (in wei). |
getFee
Retrieves the current fee required by the withdrawal precompile.
Performs a static call to the precompile. Reverts if the call fails or the response is invalid (not 32 bytes).
function getFee() public view returns (uint256 fee);
Returns
Name | Type | Description |
---|---|---|
fee | uint256 | The fee (in wei) required for a withdrawal request. |
getQueuedAmount
Returns the total amount of BERA queued for withdrawal across all unprocessed tickets.
Calculates the difference between the cumulative amount at requestLength
and requestsFinalisedUntil
.
Returns 0 if requestLength == requestsFinalisedUntil
(no unprocessed tickets) or requestLength == 0
(no tickets queued).
Assumes tickets from requestsFinalisedUntil + 1
to requestLength
are in QUEUED
state, as enforced by process
.
function getQueuedAmount() public view returns (uint256 queuedAmount);
Returns
Name | Type | Description |
---|---|---|
queuedAmount | uint256 | The total amount of BERA (in wei) in QUEUED tickets from requestsFinalisedUntil + 1 to requestLength . |
getRequestsToProcess
Calculates the highest request ID that can be finalized by process
given the current reserves.
Iterates through unprocessed tickets (requestsFinalisedUntil + 1
to requestLength
) to find the maximum number of requests whose cumulative amount does not exceed reserves()
.
Returns requestsFinalisedUntil
if no additional tickets can be processed due to insufficient reserves.
function getRequestsToProcess()
external
view
returns (uint256 newRequestsFinalisedUntil);
Returns
Name | Type | Description |
---|---|---|
newRequestsFinalisedUntil | uint256 | The highest requestId (inclusive) that can be processed without exceeding available reserves, or 0 if no tickets can be processed. |
getTotalPendingWithdrawals
Sums all current pending withdrawals as helper for keeper to calculate how much needs to be executed next
Iterates through pending withdrawals, counting only those that have not expired (fulfilled)
function getTotalPendingWithdrawals(bytes32 pubkeyHash)
public
view
returns (uint256 total);
Parameters
Name | Type | Description |
---|---|---|
pubkeyHash | bytes32 | keccak256 of public key for validator to get pending withdrawals for |
Returns
Name | Type | Description |
---|---|---|
total | uint256 | Sum amount in bera, pending on CL to return to contract |
queue
Queues a withdrawal request for BERA from the consensus layer.
*Only callable by the InfraredBERA
contract or a keeper. Reverts if:
- Caller is unauthorized (
msg.sender
is neitherInfraredBERA
nor a keeper). - Receiver is invalid (keeper cannot queue for non-depositor, non-keeper cannot queue for depositor).
- Amount is zero for non-depositor or exceeds
InfraredBERA.confirmed()
balance.*
function queue(address receiver, uint256 amount)
external
whenNotPaused
returns (uint256 requestId);
Parameters
Name | Type | Description |
---|---|---|
receiver | address | The address to receive the withdrawn BERA (user or depositor for rebalancing). |
amount | uint256 | The amount of BERA to withdraw (in wei). |
Returns
Name | Type | Description |
---|---|---|
requestId | uint256 | The unique ID assigned to the withdrawal request (starts at 1). |
execute
Executes a withdrawal request via the EIP-7002 precompile.
*Rules for withdrawals:
- Pass 0 amount for full exit
- If not full exit withdrawal amount must leave validator in active state, which means stake must remain above 250k*
*Only callable by a keeper (onlyKeeper
). Reverts if:
- Exceeds validator stake, is not divisible by 1 gwei, or exceeds
uint64
max. - Precompile call fails or returns invalid fee data.
- Provided
msg.value
is less than the precompile fee.*
Refunds excess msg.value
to the keeper after the precompile call.
*References:
- https://eips.ethereum.org/EIPS/eip-7002
- https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4788.md*
function execute(
BeaconRootsVerify.BeaconBlockHeader calldata header,
BeaconRootsVerify.Validator calldata validator,
bytes32[] calldata validatorMerkleWitness,
bytes32[] calldata balanceMerkleWitness,
uint256 validatorIndex,
bytes32 balanceLeaf,
uint256 amount,
uint256 nextBlockTimestamp
) external payable onlyKeeper whenNotPaused;
Parameters
Name | Type | Description |
---|---|---|
header | BeaconRootsVerify.BeaconBlockHeader | The Beacon block header data |
validator | BeaconRootsVerify.Validator | The full validator struct to deposit for |
validatorMerkleWitness | bytes32[] | Merkle witness for validator |
balanceMerkleWitness | bytes32[] | Merkle witness for balance container |
validatorIndex | uint256 | index of validator in beacon state tree |
balanceLeaf | bytes32 | 32 bytes chunk including packed balance |
amount | uint256 | The amount of BERA to withdraw (in wei). |
nextBlockTimestamp | uint256 | timestamp of following block to header to verify parent root in beaconroots call |
process
Finalizes a range of withdrawal requests, marking them as claimable or rebalancing to the depositor.
*Reverts if:
newRequestsFinalisedUntil
exceedsrequestLength
.newRequestsFinalisedUntil
is less than or equal torequestsFinalisedUntil
.- Available reserves are insufficient for the total amount to finalize.*
Accumulates amounts for depositor rebalancing into a single call to InfraredBERADepositor.queue
.
Updates totalClaimable
for non-depositor tickets.
function process(uint256 newRequestsFinalisedUntil)
external
onlyKeeper
whenNotPaused;
Parameters
Name | Type | Description |
---|---|---|
newRequestsFinalisedUntil | uint256 | The highest requestId to finalize (inclusive). |
claim
Claims a finalized withdrawal request for a user.
*Reverts if:
requestId
exceedsrequestsFinalisedUntil
(not finalized).- sender is not receiver or keeper
- Ticket is not in
PROCESSED
state or belongs to the depositor.*
Transitions the ticket to CLAIMED
and transfers the amount to the receiver.
function claim(uint256 requestId) external whenNotPaused;
Parameters
Name | Type | Description |
---|---|---|
requestId | uint256 | The ID of the withdrawal request to claim. |
claimBatch
Claims multiple finalized withdrawal requests for same receiver in a single transaction.
*Reverts if:
- Any
requestId
exceedsrequestsFinalisedUntil
(not finalized). - Any ticket receiver is not the same as the others
- Any ticket is not in
PROCESSED
state or belongs to the depositor. - sender is not receiver or keeper*
Transitions each ticket to CLAIMED
and transfers the total amount to the caller.
Emits a Claimed
event for each claimed ticket.
function claimBatch(uint256[] calldata requestIds, address receiver)
external
whenNotPaused;
Parameters
Name | Type | Description |
---|---|---|
requestIds | uint256[] | An array of request IDs to claim. |
receiver | address | recipient address of all requestIds |
sweepForcedExit
Handles Forced withdrawals from the CL.
*RESTRICTED USAGE: This function should ONLY be called when:
- A validator has been forced to exit from the CL.*
The funds will enter the IBERA system as a deposit via the InfraredBERADepositor.
function sweepForcedExit(
BeaconRootsVerify.BeaconBlockHeader calldata header,
BeaconRootsVerify.Validator calldata validator,
uint256 validatorIndex,
bytes32[] calldata validatorMerkleWitness,
uint256 nextBlockTimestamp
) external onlyGovernor;
Parameters
Name | Type | Description |
---|---|---|
header | BeaconRootsVerify.BeaconBlockHeader | The Beacon block header data |
validator | BeaconRootsVerify.Validator | The full validator struct to deposit for |
validatorIndex | uint256 | index of validator |
validatorMerkleWitness | bytes32[] | Merkle witness for validator |
nextBlockTimestamp | uint256 | timestamp of following block to header to verify parent root in beaconroots call |
sweepUnaccountedForFunds
Handles excess stake that was refunded from a validator due to non-IBERA (bypass) deposits exceeding MAX_EFFECTIVE_BALANCE
*RESTRICTED USAGE: This function should ONLY be called when:
- A non-IBERA entity deposits to our validator, pushing total stake above MAX_EFFECTIVE_BALANCE
- The excess stake is refunded by the CL to this contract*
The funds will enter the IBERA system as yield via the FeeReceivor
*This should NEVER be used for:
- Validators exited due to falling out of the validator set*
Note: access: Only callable by governance
function sweepUnaccountedForFunds(uint256 amount) external onlyGovernor;
Parameters
Name | Type | Description |
---|---|---|
amount | uint256 | The amount of excess stake to sweep |
receive
receive() external payable;
setMinActivationBalance
function setMinActivationBalance(uint256 _minActivationBalance)
external
onlyGovernor;
totalPendingWithdrawals
Internal function to get total pending withdrawals over all validators and remove outdated ones
function totalPendingWithdrawals() internal returns (uint256 total);