145 lines
5.3 KiB
Solidity
145 lines
5.3 KiB
Solidity
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
pragma solidity ^0.6.0;
|
|
|
|
// This interface is designed to be compatible with the Vyper version.
|
|
interface IDepositContract {
|
|
event DepositEvent(
|
|
bytes pubkey,
|
|
bytes withdrawal_credentials,
|
|
bytes amount,
|
|
bytes signature,
|
|
bytes index
|
|
);
|
|
|
|
function deposit(
|
|
bytes calldata pubkey,
|
|
bytes calldata withdrawal_credentials,
|
|
bytes calldata signature,
|
|
bytes32 deposit_data_root
|
|
) external payable;
|
|
}
|
|
|
|
// This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity.
|
|
// It tries to stay as close as possible to the original source code.
|
|
contract DepositContract is IDepositContract {
|
|
uint constant GWEI = 1e9;
|
|
|
|
uint constant MIN_DEPOSIT_AMOUNT = 1000000000; // Gwei
|
|
uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32;
|
|
uint constant MAX_DEPOSIT_COUNT = 4294967295; // 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1
|
|
uint constant PUBKEY_LENGTH = 48; // bytes
|
|
uint constant WITHDRAWAL_CREDENTIALS_LENGTH = 32; // bytes
|
|
uint constant SIGNATURE_LENGTH = 96; // bytes
|
|
uint constant AMOUNT_LENGTH = 8; // bytes
|
|
|
|
bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch;
|
|
uint256 deposit_count;
|
|
|
|
// TODO: use immutable for this
|
|
bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes;
|
|
|
|
// Compute hashes in empty sparse Merkle tree
|
|
constructor() public {
|
|
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++)
|
|
zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height]));
|
|
}
|
|
|
|
function get_deposit_root() external view returns (bytes32) {
|
|
bytes24 zero_bytes24;
|
|
bytes32 node;
|
|
uint size = deposit_count;
|
|
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {
|
|
if ((size & 1) == 1)
|
|
node = sha256(abi.encodePacked(branch[height], node));
|
|
else
|
|
node = sha256(abi.encodePacked(node, zero_hashes[height]));
|
|
size /= 2;
|
|
}
|
|
return sha256(abi.encodePacked(
|
|
node,
|
|
to_little_endian_64(uint64(deposit_count)),
|
|
zero_bytes24
|
|
));
|
|
}
|
|
|
|
function get_deposit_count() external view returns (bytes memory) {
|
|
return to_little_endian_64(uint64(deposit_count));
|
|
}
|
|
|
|
function deposit(
|
|
bytes calldata pubkey,
|
|
bytes calldata withdrawal_credentials,
|
|
bytes calldata signature,
|
|
bytes32 deposit_data_root
|
|
) override external payable {
|
|
// Avoid overflowing the Merkle tree (and prevent edge case in computing `self.branch`)
|
|
require(deposit_count < MAX_DEPOSIT_COUNT);
|
|
|
|
// Check deposit amount
|
|
uint deposit_amount = msg.value / GWEI;
|
|
require(deposit_amount >= MIN_DEPOSIT_AMOUNT);
|
|
|
|
// Length checks for safety
|
|
require(pubkey.length == PUBKEY_LENGTH);
|
|
require(withdrawal_credentials.length == WITHDRAWAL_CREDENTIALS_LENGTH);
|
|
require(signature.length == SIGNATURE_LENGTH);
|
|
|
|
// FIXME: these are not the Vyper code, but should verify they are not needed
|
|
// assert(deposit_amount <= 2**64-1);
|
|
// assert(deposit_count <= 2**64-1);
|
|
|
|
// Emit `DepositEvent` log
|
|
bytes memory amount = to_little_endian_64(uint64(deposit_amount));
|
|
emit DepositEvent(
|
|
pubkey,
|
|
withdrawal_credentials,
|
|
amount,
|
|
signature,
|
|
to_little_endian_64(uint64(deposit_count))
|
|
);
|
|
|
|
// Compute deposit data root (`DepositData` hash tree root)
|
|
// These are helpers and are implicitly initialised to zero.
|
|
bytes16 zero_bytes16;
|
|
bytes24 zero_bytes24;
|
|
bytes32 zero_bytes32;
|
|
bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, zero_bytes16));
|
|
bytes32 signature_root = sha256(abi.encodePacked(
|
|
sha256(abi.encodePacked(bytes(signature[:64]))),
|
|
sha256(abi.encodePacked(bytes(signature[64:]), zero_bytes32))
|
|
));
|
|
bytes32 node = sha256(abi.encodePacked(
|
|
sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)),
|
|
sha256(abi.encodePacked(amount, zero_bytes24, signature_root))
|
|
));
|
|
// Verify computed and expected deposit data roots match
|
|
require(node == deposit_data_root);
|
|
|
|
// Add deposit data root to Merkle tree (update a single `branch` node)
|
|
deposit_count += 1;
|
|
uint size = deposit_count;
|
|
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {
|
|
if ((size & 1) == 1) {
|
|
branch[height] = node;
|
|
break;
|
|
}
|
|
node = sha256(abi.encodePacked(branch[height], node));
|
|
size /= 2;
|
|
}
|
|
}
|
|
|
|
function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) {
|
|
// Unrolled the loop here.
|
|
ret = new bytes(8);
|
|
ret[0] = bytes1(uint8(value & 0xff));
|
|
ret[1] = bytes1(uint8((value >> 8) & 0xff));
|
|
ret[2] = bytes1(uint8((value >> 16) & 0xff));
|
|
ret[3] = bytes1(uint8((value >> 24) & 0xff));
|
|
ret[4] = bytes1(uint8((value >> 32) & 0xff));
|
|
ret[5] = bytes1(uint8((value >> 40) & 0xff));
|
|
ret[6] = bytes1(uint8((value >> 48) & 0xff));
|
|
ret[7] = bytes1(uint8((value >> 56) & 0xff));
|
|
}
|
|
}
|