|
| 1 | +// ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━ |
| 2 | +// ┃┏━━┛┏┛┗┓┃┃━━┃┏━┓┃━━┃┏━┓┃━━━━┗┓┏┓┃━━━━━━━━━━━━━━━━━━┏┛┗┓━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━━━━━━┏┛┗┓ |
| 3 | +// ┃┗━━┓┗┓┏┛┃┗━┓┗┛┏┛┃━━┃┃━┃┃━━━━━┃┃┃┃┏━━┓┏━━┓┏━━┓┏━━┓┏┓┗┓┏┛━━━━┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓━┏━━┓┗┓┏┛ |
| 4 | +// ┃┏━━┛━┃┃━┃┏┓┃┏━┛┏┛━━┃┃━┃┃━━━━━┃┃┃┃┃┏┓┃┃┏┓┃┃┏┓┃┃━━┫┣┫━┃┃━━━━━┃┃━┏┓┃┏┓┃┃┏┓┓━┃┃━┃┏┛┗━┓┃━┃┏━┛━┃┃━ |
| 5 | +// ┃┗━━┓━┃┗┓┃┃┃┃┃┃┗━┓┏┓┃┗━┛┃━━━━┏┛┗┛┃┃┃━┫┃┗┛┃┃┗┛┃┣━━┃┃┃━┃┗┓━━━━┃┗━┛┃┃┗┛┃┃┃┃┃━┃┗┓┃┃━┃┗┛┗┓┃┗━┓━┃┗┓ |
| 6 | +// ┗━━━┛━┗━┛┗┛┗┛┗━━━┛┗┛┗━━━┛━━━━┗━━━┛┗━━┛┃┏━┛┗━━┛┗━━┛┗┛━┗━┛━━━━┗━━━┛┗━━┛┗┛┗┛━┗━┛┗┛━┗━━━┛┗━━┛━┗━┛ |
| 7 | +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
| 8 | +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
| 9 | + |
| 10 | +// SPDX-License-Identifier: CC0-1.0 |
| 11 | + |
| 12 | +// This interface is designed to be compatible with the Vyper version. |
| 13 | +/// @notice This is the Ethereum 2.0 deposit contract interface. |
| 14 | +/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs |
| 15 | +interface IDepositContract { |
| 16 | + /// @notice A processed deposit event. |
| 17 | + event DepositEvent( |
| 18 | + bytes pubkey, |
| 19 | + bytes withdrawal_credentials, |
| 20 | + bytes amount, |
| 21 | + bytes signature, |
| 22 | + bytes index |
| 23 | + ); |
| 24 | + |
| 25 | + /// @notice Submit a Phase 0 DepositData object. |
| 26 | + /// @param pubkey A BLS12-381 public key. |
| 27 | + /// @param withdrawal_credentials Commitment to a public key for withdrawals. |
| 28 | + /// @param signature A BLS12-381 signature. |
| 29 | + /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. |
| 30 | + /// Used as a protection against malformed input. |
| 31 | + function deposit( |
| 32 | + bytes calldata pubkey, |
| 33 | + bytes calldata withdrawal_credentials, |
| 34 | + bytes calldata signature, |
| 35 | + bytes32 deposit_data_root |
| 36 | + ) external payable; |
| 37 | + |
| 38 | + /// @notice Query the current deposit root hash. |
| 39 | + /// @return The deposit root hash. |
| 40 | + function get_deposit_root() external view returns (bytes32); |
| 41 | + |
| 42 | + /// @notice Query the current deposit count. |
| 43 | + /// @return The deposit count encoded as a little endian 64-bit number. |
| 44 | + function get_deposit_count() external view returns (bytes memory); |
| 45 | +} |
| 46 | + |
| 47 | +// Based on official specification in https://eips.ethereum.org/EIPS/eip-165 |
| 48 | +interface ERC165 { |
| 49 | + /// @notice Query if a contract implements an interface |
| 50 | + /// @param interfaceId The interface identifier, as specified in ERC-165 |
| 51 | + /// @dev Interface identification is specified in ERC-165. This function |
| 52 | + /// uses less than 30,000 gas. |
| 53 | + /// @return `true` if the contract implements `interfaceId` and |
| 54 | + /// `interfaceId` is not 0xffffffff, `false` otherwise |
| 55 | + function supportsInterface(bytes4 interfaceId) external pure returns (bool); |
| 56 | +} |
| 57 | + |
| 58 | +// This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity. |
| 59 | +// It tries to stay as close as possible to the original source code. |
| 60 | +/// @notice This is the Ethereum 2.0 deposit contract interface. |
| 61 | +/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs |
| 62 | +contract DepositContract is IDepositContract, ERC165 { |
| 63 | + uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32; |
| 64 | + // NOTE: this also ensures `deposit_count` will fit into 64-bits |
| 65 | + uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1; |
| 66 | + |
| 67 | + bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch; |
| 68 | + uint256 deposit_count; |
| 69 | + |
| 70 | + bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes; |
| 71 | + |
| 72 | + constructor() public { |
| 73 | + // Compute hashes in empty sparse Merkle tree |
| 74 | + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++) |
| 75 | + zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height])); |
| 76 | + } |
| 77 | + |
| 78 | + function get_deposit_root() override external view returns (bytes32) { |
| 79 | + bytes32 node; |
| 80 | + uint size = deposit_count; |
| 81 | + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { |
| 82 | + if ((size & 1) == 1) |
| 83 | + node = sha256(abi.encodePacked(branch[height], node)); |
| 84 | + else |
| 85 | + node = sha256(abi.encodePacked(node, zero_hashes[height])); |
| 86 | + size /= 2; |
| 87 | + } |
| 88 | + return sha256(abi.encodePacked( |
| 89 | + node, |
| 90 | + to_little_endian_64(uint64(deposit_count)), |
| 91 | + bytes24(0) |
| 92 | + )); |
| 93 | + } |
| 94 | + |
| 95 | + function get_deposit_count() override external view returns (bytes memory) { |
| 96 | + return to_little_endian_64(uint64(deposit_count)); |
| 97 | + } |
| 98 | + |
| 99 | + function deposit( |
| 100 | + bytes calldata pubkey, |
| 101 | + bytes calldata withdrawal_credentials, |
| 102 | + bytes calldata signature, |
| 103 | + bytes32 deposit_data_root |
| 104 | + ) override external payable { |
| 105 | + // Extended ABI length checks since dynamic types are used. |
| 106 | + require(pubkey.length == 48, "DepositContract: invalid pubkey length"); |
| 107 | + require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length"); |
| 108 | + require(signature.length == 96, "DepositContract: invalid signature length"); |
| 109 | + |
| 110 | + // Check deposit amount |
| 111 | + require(msg.value >= 1 ether, "DepositContract: deposit value too low"); |
| 112 | + require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei"); |
| 113 | + uint deposit_amount = msg.value / 1 gwei; |
| 114 | + require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high"); |
| 115 | + |
| 116 | + // Emit `DepositEvent` log |
| 117 | + bytes memory amount = to_little_endian_64(uint64(deposit_amount)); |
| 118 | + emit DepositEvent( |
| 119 | + pubkey, |
| 120 | + withdrawal_credentials, |
| 121 | + amount, |
| 122 | + signature, |
| 123 | + to_little_endian_64(uint64(deposit_count)) |
| 124 | + ); |
| 125 | + |
| 126 | + // Compute deposit data root (`DepositData` hash tree root) |
| 127 | + bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0))); |
| 128 | + bytes32 signature_root = sha256(abi.encodePacked( |
| 129 | + sha256(abi.encodePacked(signature[:64])), |
| 130 | + sha256(abi.encodePacked(signature[64:], bytes32(0))) |
| 131 | + )); |
| 132 | + bytes32 node = sha256(abi.encodePacked( |
| 133 | + sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)), |
| 134 | + sha256(abi.encodePacked(amount, bytes24(0), signature_root)) |
| 135 | + )); |
| 136 | + |
| 137 | + // Verify computed and expected deposit data roots match |
| 138 | + require(node == deposit_data_root, "DepositContract: reconstructed DepositData does not match supplied deposit_data_root"); |
| 139 | + |
| 140 | + // Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`) |
| 141 | + require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full"); |
| 142 | + |
| 143 | + // Add deposit data root to Merkle tree (update a single `branch` node) |
| 144 | + deposit_count += 1; |
| 145 | + uint size = deposit_count; |
| 146 | + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { |
| 147 | + if ((size & 1) == 1) { |
| 148 | + branch[height] = node; |
| 149 | + return; |
| 150 | + } |
| 151 | + node = sha256(abi.encodePacked(branch[height], node)); |
| 152 | + size /= 2; |
| 153 | + } |
| 154 | + // As the loop should always end prematurely with the `return` statement, |
| 155 | + // this code should be unreachable. We assert `false` just to be safe. |
| 156 | + assert(false); |
| 157 | + } |
| 158 | + |
| 159 | + function supportsInterface(bytes4 interfaceId) override external pure returns (bool) { |
| 160 | + return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId; |
| 161 | + } |
| 162 | + |
| 163 | + function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) { |
| 164 | + ret = new bytes(8); |
| 165 | + bytes8 bytesValue = bytes8(value); |
| 166 | + // Byteswapping during copying to bytes. |
| 167 | + ret[0] = bytesValue[7]; |
| 168 | + ret[1] = bytesValue[6]; |
| 169 | + ret[2] = bytesValue[5]; |
| 170 | + ret[3] = bytesValue[4]; |
| 171 | + ret[4] = bytesValue[3]; |
| 172 | + ret[5] = bytesValue[2]; |
| 173 | + ret[6] = bytesValue[1]; |
| 174 | + ret[7] = bytesValue[0]; |
| 175 | + } |
| 176 | +} |
| 177 | +// ==== |
| 178 | +// compileViaYul: also |
| 179 | +// ---- |
| 180 | +// constructor() |
| 181 | +// supportsInterface(bytes4): 0x0 -> 0 |
| 182 | +// supportsInterface(bytes4): 0xffffffff00000000000000000000000000000000000000000000000000000000 -> false # defined to be false by ERC-165 # |
| 183 | +// supportsInterface(bytes4): 0x01ffc9a700000000000000000000000000000000000000000000000000000000 -> true # ERC-165 id # |
| 184 | +// supportsInterface(bytes4): 0x8564090700000000000000000000000000000000000000000000000000000000 -> true # the deposit interface id # |
| 185 | +// get_deposit_root() -> 0xd70a234731285c6804c2a4f56711ddb8c82c99740f207854891028af34e27e5e |
| 186 | +// get_deposit_count() -> 0x20, 8, 0 |
| 187 | +// # TODO: check balance and logs after each deposit # |
| 188 | +// deposit(bytes,bytes,bytes,bytes32), 32 ether: 0 -> FAILURE # Empty input # |
| 189 | +// get_deposit_root() -> 0xd70a234731285c6804c2a4f56711ddb8c82c99740f207854891028af34e27e5e |
| 190 | +// get_deposit_count() -> 0x20, 8, 0 |
| 191 | +// deposit(bytes,bytes,bytes,bytes32), 1 ether: 0x80, 0xe0, 0x120, 0xaa4a8d0b7d9077248630f1a4701ae9764e42271d7f22b7838778411857fd349e, 0x30, 0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f73292, 0x67a8811c397529dac52ae1342ba58c9500000000000000000000000000000000, 0x20, 0x00f50428677c60f997aadeab24aabf7fceaef491c96a52b463ae91f95611cf71, 0x60, 0xa29d01cc8c6296a8150e515b5995390ef841dc18948aa3e79be6d7c1851b4cbb, 0x5d6ff49fa70b9c782399506a22a85193151b9b691245cebafd2063012443c132, 0x4b6c36debaedefb7b2d71b0503ffdc00150aaffd42e63358238ec888901738b8 -> # txhash: 0x7085c586686d666e8bb6e9477a0f0b09565b2060a11f1c4209d3a52295033832 # |
| 192 | +// get_deposit_root() -> 0x2089653123d9c721215120b6db6738ba273bbc5228ac093b1f983badcdc8a438 |
| 193 | +// get_deposit_count() -> 0x20, 8, 0x0100000000000000000000000000000000000000000000000000000000000000 |
| 194 | +// deposit(bytes,bytes,bytes,bytes32), 32 ether: 0x80, 0xe0, 0x120, 0xdbd986dc85ceb382708cf90a3500f500f0a393c5ece76963ac3ed72eccd2c301, 0x30, 0xb2ce0f79f90e7b3a113ca5783c65756f96c4b4673c2b5c1eb4efc22280259441, 0x06d601211e8866dc5b50dc48a244dd7c00000000000000000000000000000000, 0x20, 0x00344b6c73f71b11c56aba0d01b7d8ad83559f209d0a4101a515f6ad54c89771, 0x60, 0x945caaf82d18e78c033927d51f452ebcd76524497b91d7a11219cb3db6a1d369, 0x7595fc095ce489e46b2ef129591f2f6d079be4faaf345a02c5eb133c072e7c56, 0x0c6c3617eee66b4b878165c502357d49485326bc6b31bc96873f308c8f19c09d -> # txhash: 0x404d8e109822ce448e68f45216c12cb051b784d068fbe98317ab8e50c58304ac # |
| 195 | +// get_deposit_root() -> 0x40255975859377d912c53aa853245ebd939bdd2b33a28e084babdcc1ed8238ee |
| 196 | +// get_deposit_count() -> 0x20, 8, 0x0200000000000000000000000000000000000000000000000000000000000000 |
0 commit comments