Skip to content

Commit

Permalink
first implem of compact batch deposit / test + debugger broken
Browse files Browse the repository at this point in the history
  • Loading branch information
0xvv committed Sep 28, 2023
1 parent 001d8ab commit 3352e3d
Show file tree
Hide file tree
Showing 8 changed files with 514 additions and 69 deletions.
155 changes: 155 additions & 0 deletions src/BatchDepositCompact.huff
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/// @title Batch Deposit
/// @notice SPDX-License-Identifier: MIT
/// @author 0xvv <https://github.com/0xvv>
/// @notice Gas efficient batch deposit contract for ETH staking

#include "../lib/huffmate/src/utils/Errors.huff"
#include "../lib/huffmate/src/utils/Calls.huff"

#define function deposit(bytes) payable returns ()

#define constant ETHER_32 = 0x1bc16d674ec800000

#define constant PUBKEY_LEN = 0x30
#define constant WITHDCRED_LEN = 0x20
#define constant SIGNATURE_LEN = 0x60
#define constant DATA_ROOT_LEN = 0x20
#define constant DEPOSIT_LEN = 0xd0 // PUBKEY_LEN + WITHDCRED_LEN + SIGNATURE_LEN + DATA_ROOT_LEN

// offsets for TEMPLATE_CALLDATA
#define constant DATAROOT_MEM_OFFSET = 0x64
#define constant PUBKEY_MEM_OFFSET = 0xa4
#define constant WITHDCRED_MEM_OFFSET = 0x104
#define constant SIGNATURE_MEM_OFFSET = 0x144

#define table TEMPLATE_CALLDATA {
0x22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060
}

// No need for function selector we assume the call is to batch deposit
#define macro MAIN() = takes(0) returns(0) {

// checking pubkeys length is the expected length and count > 0
0x24 calldataload // [deposit_length]
[DEPOSIT_LEN] // [DEPOSIT_LEN, deposit_length]
dup1 dup3 // [deposit_length, DEPOSIT_LEN, DEPOSIT_LEN, deposit_length]
mod // [deposit_length % DEPOSIT_LEN, DEPOSIT_LEN, deposit_length]
fail jumpi // revert if not equal to 0 [DEPOSIT_LEN, deposit_length]
swap1 // [deposit_length, DEPOSIT_LEN]
div // [deposit_count]
dup1 // [deposit_count, deposit_count]
iszero fail jumpi // revert if zero [deposit_count]

// check the deposit amount is correct (32 * deposit_count)
dup1 // [deposit_count, deposit_count]
[ETHER_32] mul // [deposit_count * ETHER_32, deposit_count]
callvalue // [msg.value, deposit_count * ETHER_32, deposit_count]
eq iszero fail jumpi // revert if not equal [deposit_count]

0x44 // [pubkey_start, pubkey_count]

__tablesize(TEMPLATE_CALLDATA)
__tablestart(TEMPLATE_CALLDATA)
0x00
codecopy // copy the template calldata stored after the bytecode, not consumed or cleared by CALL() so it's reused

0x00 // [i, pubkey_start, pubkey_count]
loop:
dup3 dup2 // [i, pubkey_count, i, pubkey_start, pubkey_count]
lt // [i < pubkey_count, i, pubkey_start, pubkey_count]
iszero finish jumpi // jump to finish if i >= pubkey_count [i, pubkey_start, pubkey_count]

// copy pubkey in memory and compute withdrawal creds start
swap1 // [pubkey_start, i, pubkey_count]
[PUBKEY_LEN] // [PUBKEY_LEN, pubkey_start, i, pubkey_count]
dup1 // [PUBKEY_LEN, PUBKEY_LEN, pubkey_start, i, pubkey_count]
dup3 // [pubkey_start, PUBKEY_LEN, PUBKEY_LEN, pubkey_start, i, pubkey_count]
[PUBKEY_MEM_OFFSET] // [MEM_OFFSET, pubkey_start, PUBKEY_LEN, PUBKEY_LEN, pubkey_start, i, pubkey_count]
calldatacopy // [PUBKEY_LEN, pubkey_start, i, pubkey_count]
add // [withdr_cred_start, i, pubkey_count]

// copy withdrawal creds in memory
[WITHDCRED_LEN] // [WITHDCRED_LEN, withdr_cred_start, i, pubkey_count]
dup1 // [WITHDCRED_LEN, WITHDCRED_LEN, withdr_cred_start, i, pubkey_count]
dup3 // [withdr_cred_start, WITHDCRED_LEN, WITHDCRED_LEN, withdr_cred_start, i, pubkey_count]
[WITHDCRED_MEM_OFFSET] // [MEM_OFFSET, withdr_cred_start, WITHDCRED_LEN, WITHDCRED_LEN, withdr_cred_start, i, pubkey_count]
calldatacopy // [WITHDCRED_LEN, withdr_cred_start, i, pubkey_count]
add // [signature_start, i, pubkey_count]

// copy signatures in memory
[SIGNATURE_LEN] // [SIGNATURE_LEN, signature_start, i, pubkey_count]
dup1 // [SIGNATURE_LEN, SIGNATURE_LEN, signature_start, i, pubkey_count]
dup3 // [signature_start, SIGNATURE_LEN, SIGNATURE_LEN, signature_start, i, pubkey_count]
[SIGNATURE_MEM_OFFSET] // [MEM_OFFSET, signature_start, SIGNATURE_LEN, SIGNATURE_LEN, signature_start, i, pubkey_count]
calldatacopy // [SIGNATURE_LEN, signature_start, i, pubkey_count]
add // [data_root_start, i, pubkey_count]

// copy data root in memory
[DATA_ROOT_LEN] // [DATA_ROOT_LEN, data_root_start, i, pubkey_count]
dup1 // [DATA_ROOT_LEN, DATA_ROOT_LEN, data_root_start, i, pubkey_count]
dup3 // [data_root_start, DATA_ROOT_LEN, DATA_ROOT_LEN, data_root_start, i, pubkey_count]
[DATAROOT_MEM_OFFSET] // [MEM_OFFSET, data_root_start, DATA_ROOT_LEN, DATA_ROOT_LEN, data_root_start, i, pubkey_count]
calldatacopy // [DATA_ROOT_LEN, data_root_start, i, pubkey_count]
add // [next_pubkey_start, i, pubkey_count]

// calldata len v v 32 ether v Deposit contract address v 65,535 gas
CALL(0x00, 0x00, 0x1e4, 0x00, 0x1bc16d674ec800000, 0x00000000219ab540356cbb839cbe05303d7705fa, 0xFFFF)
iszero fail jumpi // jump to fail if call failed
// increment i
swap1 0x01 add // [i+1, next_pubkey_start, pubkey_count]

loop jump
finish:
0x00 0x00 return

fail:
0x00 0x00 revert
}


/* expected calldata to this contract with 1 validator
concatanation of public key, withdrawal cred, signature, deposit data root

-> % c cd 'deposit(bytes)' 0xadf1993ae1b10e580cedde629210792dba6dbb6489736e9300467c4373c73f3f03ac15ce3e7ac72282879e8dda4684f600bad2ba77e455948fe647461b335fa2d8e9d3fd36bd671754c6aafb65c886f888a94fbeb921a2acbe9cfae04f7694c57104d386ef799549582ca4986d8cb9d671b7b482146b83708ee3037409dda59b16d3f955c1bd08d0483affd1bcec334310f884fd2203282ddb55c55950ffff97981162ba9b7a67cb713afcc2c7fe5f2a044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d
0x98b1e06a0000000000000000000000000000000000000000000000000000000000000020 0x04 Offset of bytes
00000000000000000000000000000000000000000000000000000000000000d0 0x24 ([0x04] + 0x04) Length of bytes
adf1993ae1b10e580cedde629210792dba6dbb6489736e9300467c4373c73f3f 0x44 Start of bytes
03ac15ce3e7ac72282879e8dda4684f600bad2ba77e455948fe647461b335fa2
d8e9d3fd36bd671754c6aafb65c886f888a94fbeb921a2acbe9cfae04f7694c5
7104d386ef799549582ca4986d8cb9d671b7b482146b83708ee3037409dda59b
16d3f955c1bd08d0483affd1bcec334310f884fd2203282ddb55c55950ffff97
981162ba9b7a67cb713afcc2c7fe5f2a044852b2a670ade5407e78fb2863c51d
e9fcb96542a07186fe3aeda6bb8a116d00000000000000000000000000000000


"deposit(bytes,bytes,bytes,bytes32)"
real examples :
0x228951180000000000000000000000000000000000000000000000000000000000000080 offset PUBKEY // 0x04
00000000000000000000000000000000000000000000000000000000000000e0 offset WITHDRAWAL_CRED // 0x24
0000000000000000000000000000000000000000000000000000000000000120 offset SIGNATURE // 0x44
06fd86fa59bd643d7e30b0c7be1bbf6fcde4c8dce5cbbf3030cf0ed8f067fa53 DEPOSIT_DATA_ROOT (32 bytes) // 0x64
0000000000000000000000000000000000000000000000000000000000000030 PUBKEY_LEN // 0x84
adf1993ae1b10e580cedde629210792dba6dbb6489736e9300467c4373c73f3f PUBLIC_KEY (48 bytes) // 0xa4
03ac15ce3e7ac72282879e8dda4684f600000000000000000000000000000000 ^ // 0xc4
0000000000000000000000000000000000000000000000000000000000000020 WITHDRAWAL_CREDS_LEN // 0xe4
00bad2ba77e455948fe647461b335fa2d8e9d3fd36bd671754c6aafb65c886f8 WITHDRAWAL_CREDS (32 bytes) // 0x104
0000000000000000000000000000000000000000000000000000000000000060 SIGNATURE_LEN // 0x124
88a94fbeb921a2acbe9cfae04f7694c57104d386ef799549582ca4986d8cb9d6 SIGNATURE (96 bytes) // 0x144
71b7b482146b83708ee3037409dda59b16d3f955c1bd08d0483affd1bcec3343 ^ // 0x164
10f884fd2203282ddb55c55950ffff97981162ba9b7a67cb713afcc2c7fe5f2a ^ // 0x184
// 0x1a4 total length 420 bytes

0x228951180000000000000000000000000000000000000000000000000000000000000080
00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000120
51ec166823079379055f1c1845be0a5f78d08299e95088aaa9d20b787e7eb464
0000000000000000000000000000000000000000000000000000000000000030
95dbc8eba0bc56c073daeb40ea12ee556498f7f6dd79b1ece77fb37e5bb87110
be6b047c48f6c6e77fda9d72716f158d00000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000020
010000000000000000000000e839a3e9efb32c6a56ab7128e51056585275506c
0000000000000000000000000000000000000000000000000000000000000060
b757a3b22cb3dd15c40976e6d5a6198f0d4cccafaf443583730de1c553844308
e63761a3fb73252d54f1909e604309c90c5f728ade49c087728c6f364bdf35b9
d23c9769b0230dc345f644e13ccf82d627a086ac0cdfb6c9ed25ad5d16ef5303
*/
40 changes: 0 additions & 40 deletions src/SimpleStore.huff

This file was deleted.

26 changes: 26 additions & 0 deletions src/interface/IDeposit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// @notice Interface of the official Deposit contract from the ETH
/// Foundation.
interface IDeposit {
event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index);

/// @notice Submit a Phase 0 DepositData object.
///
/// @param pubkey - A BLS12-381 public key.
/// @param withdrawal_credentials - Commitment to a public key for withdrawals.
/// @param 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 pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) external payable;

/// @notice Return the deposit count.
function deposit_count() external view returns (uint256);
}
93 changes: 93 additions & 0 deletions src/lib/LibBytes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: 2023 Kiln <[email protected]>
//
// ██╗ ██╗██╗██╗ ███╗ ██╗
// ██║ ██╔╝██║██║ ████╗ ██║
// █████╔╝ ██║██║ ██╔██╗ ██║
// ██╔═██╗ ██║██║ ██║╚██╗██║
// ██║ ██╗██║███████╗██║ ╚████║
// ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝
//
pragma solidity >=0.8.17;

/// @title Lib Bytes
/// @notice This library helps manipulating bytes
library LibBytes {
/// @notice The length overflows an uint
error SliceOverflow();

/// @notice The slice is outside of the initial bytes bounds
error SliceOutOfBounds();

/// @notice Slices the provided bytes
/// @param bytes_ Bytes to slice
/// @param start The starting index of the slice
/// @param length The length of the slice
/// @return The slice of _bytes starting at _start of length _length
// slither-disable-next-line dead-code
function slice(bytes memory bytes_, uint256 start, uint256 length) internal pure returns (bytes memory) {
unchecked {
if (length + 31 < length) {
revert SliceOverflow();
}
}
if (bytes_.length < start + length) {
revert SliceOutOfBounds();
}

bytes memory tempBytes;

// slither-disable-next-line assembly
assembly {
switch iszero(length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)

// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(length, 31)

// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
let end := add(mc, length)

for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(add(add(bytes_, lengthmod), mul(0x20, iszero(lengthmod))), start)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} { mstore(mc, mload(cc)) }

mstore(tempBytes, length)

//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
//zero out the 32 bytes slice we are about to return
//we need to do it because Solidity does not garbage collect
mstore(tempBytes, 0)

mstore(0x40, add(tempBytes, 0x20))
}
}

return tempBytes;
}
}
Loading

0 comments on commit 3352e3d

Please sign in to comment.