TBD
TBD
Constant | Value |
---|---|
FORK_BLOCK |
TBD |
DEPOSIT_CONTRACT_ADDRESS |
0x00000000219ab540356cBB839Cbe05303d7705Fa |
WITHDRAWAL_CONTRACT_ADDRESS |
TBD |
WITHDRAWAL_CODE_HASH |
TBD |
On FORK_BLOCK
the code hash of the account WITHDRAWAL_CONTRACT_ADDRESS
must equal to WITHDRAWAL_CODE_HASH
, otherwise the block is invalid.
After FORK_BLOCK
, before processing any transactions in a block, apply the following changes to the state:
DEPOSIT_CONTRACT_ADDRESS
to 0.WITHDRAWAL_CONTRACT_ADDRESS
to blockHeader.totalBeaconValidatorBalance
.Starting FORK_BLOCK
the block header will contain one more field, called the totalBeaconValidatorBalancer
at the last position. This field must be set to sum(BeaconState.balances)
.
pragma solidity 0.8.12;
contract WithdrawalContract {
struct WithdrawalReceipt {
uint64 index;
address recipient;
uint64 amount;
}
error AlreadySpent();
error InvalidProof();
/// A bitmap of the spent receipt indexes.
mapping (uint256 => uint256) public spent;
function withdraw(WithdrawalReceipt calldata receipt, bytes calldata merkle_branch, uint256 merkle_root_slot) external {
uint256 slot_index = receipt.index / 256;
uint256 slot_bit = 1 << (receipt.index % 256);
uint256 slot = spent[slot_index]; // Keeping a copy here is an SLOAD optimisation
if (slot & slot_bit) revert AlreadySpent();
// Reassemble SSZ-encoded leaf
bytes memory leaf = ssz_hash_tree_root(receipt);
bytes32 beacon_root;
assembly {
// This is the beaconstateroot(slot) instruction from EIP-4788.
beacon_root := verbatim_1i_1o(hex"48", merkle_root_slot)
}
// Calculate proof of the branch
... using leaf + merkle_branch
if (calculated_root != beacon_root) revert InvalidProof();
// Mark as spent
spent[slot_index] = slot | slot_bit;
// TODO: use low-level transfer or the `TRANSFER` opcode (the potential future of `SELFDESTRUCT`) here?
receipt.recipient.transfer(receipt.amount * 1 gwei);
}
}
This code is compiled using the specified Solidity version and settings (TBD) resulting in runtime bytecode equaling to WITHDRAWAL_CODE_HASH
.
We ensure that on FORK_BLOCK
the correct contract is exists in place of the WITHDRAWAL_CONTRACT_ADDRESS
.
Alternatively we could choose to insert the code at the address on the FORK_BLOCK
.
In order to avoid special account rules, the withdrawal contract must be able to send an Ether balance. It would be possible to set once an arbitrary large balance for the account, and not touch anything else.
However we chose to keep total supply accounting in balance, by modifying the supply of both the deposit and withdrawal contracts at the beginning of the block. Transactions in a block can modify the balances by properly depositing and withdrawing, and so their balances should stay valid throughout the block.
There are two cases these balances could get out of sync:
selfdestruct
Should these happen, these balances will vanish from the chain. A side-effect is that these two accounts become the ultimate burner addresses, instead of the commonly used 0x0000000000000000000000000000000000000000
account (among others).
This change will affect the balance of the deposit contract. Other contracts and services depending on this value may see some disruption. If they assume the balance can only grow, their assumption will be broken.
TBA
Copyright and related rights waived via CC0.