-
Notifications
You must be signed in to change notification settings - Fork 6
Deposit contract + actions #324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
b8dbe3e
deposit contract
ameya-deshmukh c0f454b
sforge fmt
ameya-deshmukh a6fcac7
matthias updated contract
ameya-deshmukh 57040c7
ok
ameya-deshmukh bb9de34
hmmm
ameya-deshmukh 9b98a19
as any for now, resolve type issue later
ameya-deshmukh fb10d9a
type issue
ameya-deshmukh cbccef1
ok better
ameya-deshmukh 16b7cfd
read functions + prettier
ameya-deshmukh 13f82dc
fix case
ameya-deshmukh 5868531
better
ameya-deshmukh baa9b1b
resolveJsonModule true
ameya-deshmukh 37561fa
prettier
ameya-deshmukh 4e4ee12
add in json to types
ameya-deshmukh 7159c5e
type error returns
ameya-deshmukh 6e7c6ab
as any for now
ameya-deshmukh e2fc7eb
extend shieldedpublic and wallet client actions
ameya-deshmukh c5e9f59
lint
ameya-deshmukh fc022ee
rm unnecessary config types
ameya-deshmukh 435bef7
add missing event etc
ameya-deshmukh f3272cd
lint
ameya-deshmukh 8241ac0
rm json from tsconfig+types config
ameya-deshmukh d4abec4
DRY
ameya-deshmukh 82e5382
testing
ameya-deshmukh 5fbfa6f
ok all tests pass
ameya-deshmukh 1a40e24
better
ameya-deshmukh 8310a52
Merge branch 'main' into ameya/deposit-contract
ameya-deshmukh 0300ca0
default deposit contract
cdrappi 41d7380
Merge branch 'main' into ameya/deposit-contract
cdrappi efabeeb
new vser
cdrappi b2e30c5
newlines
cdrappi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,4 @@ | |
| src = "src" | ||
| out = "out" | ||
| libs = ["lib"] | ||
| via_ir = true | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| // ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━ | ||
| // ┃┏━━┛┏┛┗┓┃┃━━┃┏━┓┃━━┃┏━┓┃━━━━┗┓┏┓┃━━━━━━━━━━━━━━━━━━┏┛┗┓━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━━━━━━┏┛┗┓ | ||
| // ┃┗━━┓┗┓┏┛┃┗━┓┗┛┏┛┃━━┃┃━┃┃━━━━━┃┃┃┃┏━━┓┏━━┓┏━━┓┏━━┓┏┓┗┓┏┛━━━━┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓━┏━━┓┗┓┏┛ | ||
| // ┃┏━━┛━┃┃━┃┏┓┃┏━┛┏┛━━┃┃━┃┃━━━━━┃┃┃┃┃┏┓┃┃┏┓┃┃┏┓┃┃━━┫┣┫━┃┃━━━━━┃┃━┏┓┃┏┓┃┃┏┓┓━┃┃━┃┏┛┗━┓┃━┃┏━┛━┃┃━ | ||
| // ┃┗━━┓━┃┗┓┃┃┃┃┃┃┗━┓┏┓┃┗━┛┃━━━━┏┛┗┛┃┃┃━┫┃┗┛┃┃┗┛┃┣━━┃┃┃━┃┗┓━━━━┃┗━┛┃┃┗┛┃┃┃┃┃━┃┗┓┃┃━┃┗┛┗┓┃┗━┓━┃┗┓ | ||
| // ┗━━━┛━┗━┛┗┛┗┛┗━━━┛┗┛┗━━━┛━━━━┗━━━┛┗━━┛┃┏━┛┗━━┛┗━━┛┗┛━┗━┛━━━━┗━━━┛┗━━┛┗┛┗┛━┗━┛┗┛━┗━━━┛┗━━┛━┗━┛ | ||
| // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
| // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
|
|
||
| // SPDX-License-Identifier: CC0-1.0 | ||
|
|
||
| pragma solidity ^0.8.30; | ||
|
|
||
| // This interface is designed to be compatible with the Vyper version. | ||
| /// @notice This is the Ethereum 2.0 deposit contract interface. | ||
| /// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs | ||
| interface IDepositContract { | ||
| /// @notice A processed deposit event. | ||
| event DepositEvent( | ||
| bytes node_pubkey, | ||
| bytes consensus_pubkey, | ||
| bytes withdrawal_credentials, | ||
| bytes amount, | ||
| bytes node_signature, | ||
| bytes consensus_signature, | ||
| bytes index | ||
| ); | ||
|
|
||
| /// @notice Submit a Phase 0 DepositData object. | ||
| /// @param node_pubkey An ED25519 public key. | ||
| /// @param consensus_pubkey A BLS12-381 public key. | ||
| /// @param withdrawal_credentials Commitment to a public key for withdrawals. | ||
| /// @param node_signature An ED25519 signature. | ||
| /// @param consensus_signature A BLS12-381 signature. | ||
| /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. | ||
| /// Used as a protection against malformed input. | ||
| function deposit( | ||
| bytes calldata node_pubkey, | ||
| bytes calldata consensus_pubkey, | ||
| bytes calldata withdrawal_credentials, | ||
| bytes calldata node_signature, | ||
| bytes calldata consensus_signature, | ||
| bytes32 deposit_data_root | ||
| ) external payable; | ||
|
|
||
| /// @notice Query the current deposit root hash. | ||
| /// @return The deposit root hash. | ||
| function get_deposit_root() external view returns (bytes32); | ||
|
|
||
| /// @notice Query the current deposit count. | ||
| /// @return The deposit count encoded as a little endian 64-bit number. | ||
| function get_deposit_count() external view returns (bytes memory); | ||
| } | ||
|
|
||
| // Based on official specification in https://eips.ethereum.org/EIPS/eip-165 | ||
| interface ERC165 { | ||
| /// @notice Query if a contract implements an interface | ||
| /// @param interfaceId The interface identifier, as specified in ERC-165 | ||
| /// @dev Interface identification is specified in ERC-165. This function | ||
| /// uses less than 30,000 gas. | ||
| /// @return `true` if the contract implements `interfaceId` and | ||
| /// `interfaceId` is not 0xffffffff, `false` otherwise | ||
| function supportsInterface(bytes4 interfaceId) external pure returns (bool); | ||
| } | ||
|
|
||
| // 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. | ||
| /// @notice This is the Ethereum 2.0 deposit contract interface. | ||
| /// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs | ||
| contract DepositContract is IDepositContract, ERC165 { | ||
| uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32; | ||
| // NOTE: this also ensures `deposit_count` will fit into 64-bits | ||
| uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1; | ||
|
|
||
| bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch; | ||
| uint256 deposit_count; | ||
|
|
||
| bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes; | ||
|
|
||
| constructor() { | ||
| // Compute hashes in empty sparse Merkle tree | ||
| 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() override external view returns (bytes32) { | ||
| 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)), | ||
| bytes24(0) | ||
| )); | ||
| } | ||
|
|
||
| function get_deposit_count() override external view returns (bytes memory) { | ||
| return to_little_endian_64(uint64(deposit_count)); | ||
| } | ||
|
|
||
| function deposit( | ||
| bytes calldata node_pubkey, | ||
| bytes calldata consensus_pubkey, | ||
| bytes calldata withdrawal_credentials, | ||
| bytes calldata node_signature, | ||
| bytes calldata consensus_signature, | ||
| bytes32 deposit_data_root | ||
| ) override external payable { | ||
| // Extended ABI length checks since dynamic types are used. | ||
| require(node_pubkey.length == 32, "DepositContract: invalid node_pubkey length"); | ||
| require(consensus_pubkey.length == 48, "DepositContract: invalid consensus_pubkey length"); | ||
| require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length"); | ||
| require(node_signature.length == 64, "DepositContract: invalid node_signature length"); | ||
| require(consensus_signature.length == 96, "DepositContract: invalid consensus_signature length"); | ||
|
|
||
| // Check deposit amount | ||
| require(msg.value >= 1 ether, "DepositContract: deposit value too low"); | ||
| require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei"); | ||
| uint deposit_amount = msg.value / 1 gwei; | ||
| require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high"); | ||
|
|
||
| // Emit `DepositEvent` log | ||
| bytes memory amount = to_little_endian_64(uint64(deposit_amount)); | ||
| emit DepositEvent( | ||
| node_pubkey, | ||
| consensus_pubkey, | ||
| withdrawal_credentials, | ||
| amount, | ||
| node_signature, | ||
| consensus_signature, | ||
| to_little_endian_64(uint64(deposit_count)) | ||
| ); | ||
|
|
||
| // Compute deposit data root (`DepositData` hash tree root) | ||
| bytes32 consensus_pubkey_hash = sha256(abi.encodePacked(consensus_pubkey, bytes16(0))); | ||
| bytes32 pubkey_root = sha256(abi.encodePacked(node_pubkey, consensus_pubkey_hash)); | ||
| bytes32 node_signature_hash = sha256(node_signature); | ||
| bytes32 consensus_signature_hash = sha256(abi.encodePacked( | ||
| sha256(abi.encodePacked(consensus_signature[:64])), | ||
| sha256(abi.encodePacked(consensus_signature[64:], bytes32(0))) | ||
| )); | ||
| bytes32 signature_root = sha256(abi.encodePacked(node_signature_hash, consensus_signature_hash)); | ||
| bytes32 node = sha256(abi.encodePacked( | ||
| sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)), | ||
| sha256(abi.encodePacked(amount, bytes24(0), signature_root)) | ||
| )); | ||
|
|
||
| // Verify computed and expected deposit data roots match | ||
| require(node == deposit_data_root, "DepositContract: reconstructed DepositData does not match supplied deposit_data_root"); | ||
|
|
||
| // Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`) | ||
| require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full"); | ||
|
|
||
| // 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; | ||
| return; | ||
| } | ||
| node = sha256(abi.encodePacked(branch[height], node)); | ||
| size /= 2; | ||
| } | ||
| // As the loop should always end prematurely with the `return` statement, | ||
| // this code should be unreachable. We assert `false` just to be safe. | ||
| assert(false); | ||
| } | ||
|
|
||
| function supportsInterface(bytes4 interfaceId) override external pure returns (bool) { | ||
| return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId; | ||
| } | ||
|
|
||
| function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) { | ||
| ret = new bytes(8); | ||
| bytes8 bytesValue = bytes8(value); | ||
| // Byteswapping during copying to bytes. | ||
| ret[0] = bytesValue[7]; | ||
| ret[1] = bytesValue[6]; | ||
| ret[2] = bytesValue[5]; | ||
| ret[3] = bytesValue[4]; | ||
| ret[4] = bytesValue[3]; | ||
| ret[5] = bytesValue[2]; | ||
| ret[6] = bytesValue[1]; | ||
| ret[7] = bytesValue[0]; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: SSZ Hashing Mismatch Breaks Deposit Verification
The
consensus_pubkey(48 bytes padded to 64) is hashed as a single 64-byte value, but SSZ requires splitting into two 32-byte chunks that are hashed separately before combining. This inconsistency with theconsensus_signaturehandling (which correctly splits chunks) will cause incorrect hash tree root computation and deposit verification failures.