Validator Deposit Contract

Ethereum 2.0 will not be a hard fork. Instead, users can deposit 32 ETH from the current ETH 1.0 chain into a smart contract known as the Validator Deposit Contract and run a Prysm node which will queue in the user as a validator in a full, independent proof of stake system. This allows ETH 2.0 to leverage the security pool and value of existing Ether to secure the network upon launch. Prysm nodes will listen deposit logs from this contract on the ETH 1.0 chain and detect when a validator is ready for activation.

To do this, we are deploying the Validator Deposit Contract as vyper code in the ETH 1.0 chain. This section explains how the contract works, but first, let's take a look at its most important piece: a public function called deposit.

## compiled with v0.1.0-beta.8 ##
DEPOSIT_TREE_DEPTH: constant(uint256) = 32
TWO_TO_POWER_OF_TREE_DEPTH: constant(uint256) = 4294967296 # 2**32
SECONDS_PER_DAY: constant(uint256) = 86400
MAX_64_BIT_VALUE: constant(uint256) = 18446744073709551615 # 2**64 - 1
CHAIN_START_FULL_DEPOSIT_THRESHOLD: constant(uint256) = 16384
MIN_DEPOSIT_AMOUNT: constant(uint256) = 1000000000 # Gwei
MAX_DEPOSIT_AMOUNT: constant(uint256) = 32000000000 # Gwei
Deposit: event({deposit_root: bytes32, data: bytes[528], merkle_tree_index: bytes[8], branch: bytes32[32]})
ChainStart: event({deposit_root: bytes32, time: bytes[8]})
CHAIN_START_FULL_DEPOSIT_THRESHOLD: public(uint256)
MIN_DEPOSIT_AMOUNT: public(uint256) # Gwei
MAX_DEPOSIT_AMOUNT: public(uint256) # Gwei
zerohashes: bytes32[32]
branch: bytes32[32]
deposit_count: public(uint256)
full_deposit_count: public(uint256)
genesisTime: public(bytes[8])
...
@payable
@public
def deposit(deposit_input: bytes[512]):
deposit_amount: uint256 = msg.value / as_wei_value(1, "gwei")
assert deposit_amount >= MIN_DEPOSIT_AMOUNT
assert deposit_amount <= MAX_DEPOSIT_AMOUNT
index: uint256 = self.deposit_count
deposit_timestamp: uint256 = as_unitless_number(block.timestamp)
deposit_data: bytes[528] = concat(self.to_little_endian_64(deposit_amount), self.to_little_endian_64(deposit_timestamp), deposit_input)
# add deposit to merkle tree
i: int128 = 0
power_of_two: uint256 = 2
for _ in range(32):
if (index+1) % power_of_two != 0:
break
i += 1
power_of_two *= 2
value:bytes32 = sha3(deposit_data)
for j in range(32):
if j < i:
value = sha3(concat(self.branch[j], value))
self.branch[i] = value
self.deposit_count += 1
new_deposit_root: bytes32 = self.get_deposit_root()
log.Deposit(new_deposit_root, deposit_data, self.to_little_endian_64(index), self.branch)
if deposit_amount == MAX_DEPOSIT_AMOUNT:
self.full_deposit_count += 1
if self.full_deposit_count == CHAIN_START_FULL_DEPOSIT_THRESHOLD:
timestamp_day_boundary: uint256 = as_unitless_number(block.timestamp) - as_unitless_number(block.timestamp) % SECONDS_PER_DAY + SECONDS_PER_DAY
self.genesisTime = self.to_little_endian_64(timestamp_day_boundary)
log.ChainStart(new_deposit_root, self.genesisTime)

The function takes in a deposit_input byte slice which contains important information Prysm nodes will verify about the validator.

The deposit function extracts the value of the transaction and first checks if it is between a minimum and a maximum amount. Validators can only deposit a maximum of 32 ETH, but they can also deposit smaller, incremental amounts to top off their deposit and protect themselves from losing money in the case they have low availability. Regardless, only 32 ETH will be at stake in the protocol at once, even if the validator has more than 32 deposited.

assert deposit_amount >= MIN_DEPOSIT_AMOUNT
assert deposit_amount <= MAX_DEPOSIT_AMOUN

We then construct a deposit data object which pretty much appends the raw bytes of the deposit timestamp, the value, and the deposit_input provided to the function.

index: uint256 = self.deposit_count
deposit_timestamp: uint256 = as_unitless_number(block.timestamp)
deposit_data: bytes[528] = concat(self.to_little_endian_64(deposit_amount), self.to_little_endian_64(deposit_timestamp), deposit_input)

We then add the deposit_data to a Merkle trie which will be used for Merkle proofs by Prysm nodes to verify the order of validator deposits and the fact that they exist in the trie efficiently. The code below is pretty unintuitive to add to a Merkle trie because the contract is optimized to save gas.

# add deposit to merkle tree
i: int128 = 0
power_of_two: uint256 = 2
for _ in range(32):
if (index+1) % power_of_two != 0:
break
i += 1
power_of_two *= 2
value:bytes32 = sha3(deposit_data)
for j in range(32):
if j < i:
value = sha3(concat(self.branch[j], value))
self.branch[i] = value

We then increase the number of deposits in the contract, update the Merkle trie root, and emit a log saying a deposit occurred:

self.deposit_count += 1
new_deposit_root: bytes32 = self.get_deposit_root()
log.Deposit(new_deposit_root, deposit_data, self.to_little_endian_64(index), self.branch)

Finally, we check if we did do a full deposit, AND the full deposit threshold is reached, we emit a ChainStart log which sets the genesis time of Ethereum 2.0 to be 24 hours from when the threshold was reached. This gives everyone enough time to prepare their nodes and get ready for a critical event that kicks off the network.

if deposit_amount == MAX_DEPOSIT_AMOUNT:
self.full_deposit_count += 1
if self.full_deposit_count == CHAIN_START_FULL_DEPOSIT_THRESHOLD:
timestamp_day_boundary: uint256 = as_unitless_number(block.timestamp) - as_unitless_number(block.timestamp) % SECONDS_PER_DAY + SECONDS_PER_DAY
self.genesisTime = self.to_little_endian_64(timestamp_day_boundary)
log.ChainStart(new_deposit_root, self.genesisTime)