diff --git a/src/BatchDepositCompact.huff b/src/BatchDepositCompact.huff new file mode 100644 index 0000000..f03c8bf --- /dev/null +++ b/src/BatchDepositCompact.huff @@ -0,0 +1,155 @@ +/// @title Batch Deposit +/// @notice SPDX-License-Identifier: MIT +/// @author 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 +*/ diff --git a/src/SimpleStore.huff b/src/SimpleStore.huff deleted file mode 100644 index 26d48e3..0000000 --- a/src/SimpleStore.huff +++ /dev/null @@ -1,40 +0,0 @@ -/* Interface */ -#define function setValue(uint256) nonpayable returns () -#define function getValue() view returns (uint256) - -/* Storage Slots */ -#define constant VALUE_LOCATION = FREE_STORAGE_POINTER() - -/* Methods */ -#define macro SET_VALUE() = takes (0) returns (0) { - 0x04 calldataload // [value] - [VALUE_LOCATION] // [ptr, value] - sstore // [] -} - -#define macro GET_VALUE() = takes (0) returns (0) { - // Load value from storage. - [VALUE_LOCATION] // [ptr] - sload // [value] - - // Store value in memory. - 0x00 mstore - - // Return value - 0x20 0x00 return -} - -#define macro MAIN() = takes (0) returns (0) { - // Identify which function is being called. - 0x00 calldataload 0xE0 shr - dup1 __FUNC_SIG(setValue) eq set jumpi - dup1 __FUNC_SIG(getValue) eq get jumpi - - 0x00 0x00 revert - - set: - SET_VALUE() - get: - GET_VALUE() - -} \ No newline at end of file diff --git a/src/interface/IDeposit.sol b/src/interface/IDeposit.sol new file mode 100644 index 0000000..e2b89a4 --- /dev/null +++ b/src/interface/IDeposit.sol @@ -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); +} diff --git a/src/lib/LibBytes.sol b/src/lib/LibBytes.sol new file mode 100644 index 0000000..5594f6f --- /dev/null +++ b/src/lib/LibBytes.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: BUSL-1.1 +// SPDX-FileCopyrightText: 2023 Kiln +// +// ██╗ ██╗██╗██╗ ███╗ ██╗ +// ██║ ██╔╝██║██║ ████╗ ██║ +// █████╔╝ ██║██║ ██╔██╗ ██║ +// ██╔═██╗ ██║██║ ██║╚██╗██║ +// ██║ ██╗██║███████╗██║ ╚████║ +// ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ +// +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; + } +} diff --git a/test/BatchDepositHuff.t.sol b/test/BatchDepositHuff.t.sol new file mode 100644 index 0000000..7312599 --- /dev/null +++ b/test/BatchDepositHuff.t.sol @@ -0,0 +1,83 @@ +pragma solidity 0.8.20; + +import {HuffDeployer} from "foundry-huff/HuffDeployer.sol"; +import "forge-std/Test.sol"; +import "./mock//DepositContractTestable.sol"; +import "./utils/BytesGenerator.sol"; +import "../src/lib/LibBytes.sol"; + +interface IBatchDepositBis { + function deposit(bytes calldata data) external payable; +} + +contract BatchDepositHuffTest is Test, BytesGenerator { + IBatchDepositBis huffbdo; + + address constant officialDepositContract = 0x00000000219ab540356cBB839Cbe05303d7705Fa; + + DepositContractTestable huffDepMock = DepositContractTestable(officialDepositContract); + DepositContractTestable implem = new DepositContractTestable(); + + function deployHuff() public { + vm.etch(officialDepositContract, address(implem).code); + address addr = HuffDeployer.deploy("BatchDepositCompact"); + console.log("Huff deployed", addr, addr.code.length); + assertTrue(addr != address(0)); + huffbdo = IBatchDepositBis(addr); + } + + function setUp() public { + deployHuff(); + } + + function checkDepositCount(uint256 count) internal { + assertEq(huffDepMock.deposit_count(), count); + } + + function test_d() public { + console.log("d", address(this).balance); + } + + function test_batchDeposit_match() public { + //setSalt(bytes32(abi.encodePacked())); + //c = bound(c, 1, 200); + uint256 c = 1; + uint256 COUNT = uint256(c); + + console.log("COUNT", COUNT); + + bytes memory pubkeys = genBytes(48 * COUNT); + bytes memory withdrawal_credentials = genBytes(32 * COUNT); + bytes memory signatures = genBytes(96 * COUNT); + bytes32[] memory deposit_data_roots = new bytes32[](COUNT); + bytes[] memory pubkeysList = new bytes[](COUNT); + bytes[] memory withdrawalCredentialsList = new bytes[](COUNT); + bytes[] memory signaturesList = new bytes[](COUNT); + for (uint256 i = 0; i < COUNT; i++) { + pubkeysList[i] = LibBytes.slice(pubkeys, i * 48, 48); + withdrawalCredentialsList[i] = LibBytes.slice(withdrawal_credentials, i * 32, 32); + signaturesList[i] = LibBytes.slice(signatures, i * 96, 96); + deposit_data_roots[i] = bytes32(genBytes(32)); + } + bytes memory args = new bytes(0); + for (uint256 i = 0; i < COUNT; i++) { + args = bytes.concat(args, pubkeys[i]); + args = bytes.concat(args, withdrawal_credentials[i]); + args = bytes.concat(args, signatures[i]); + args = bytes.concat(args, deposit_data_roots[i]); + } + + vm.deal(address(this), 32 ether * COUNT); + + // HUFF + huffbdo.deposit{value: 32 ether * COUNT}(args); +/* + for (uint256 i; i < COUNT; i++) { + assertEq(huffDepMock.depositDataRoots(i), deposit_data_roots[i]); + assertEq(huffDepMock.depositPubkeys(i), pubkeysList[i]); + assertEq(huffDepMock.depositWithdrawalCredentials(i), withdrawalCredentialsList[i]); + assertEq(huffDepMock.depositSignatures(i), signaturesList[i]); + } + checkDepositCount(COUNT);*/ + } +} diff --git a/test/SimpleStore.t.sol b/test/SimpleStore.t.sol deleted file mode 100644 index 003763c..0000000 --- a/test/SimpleStore.t.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.15; - -import "foundry-huff/HuffDeployer.sol"; -import "forge-std/Test.sol"; -import "forge-std/console.sol"; - -contract SimpleStoreTest is Test { - /// @dev Address of the SimpleStore contract. - SimpleStore public simpleStore; - - /// @dev Setup the testing environment. - function setUp() public { - simpleStore = SimpleStore(HuffDeployer.deploy("SimpleStore")); - } - - /// @dev Ensure that you can set and get the value. - function testSetAndGetValue(uint256 value) public { - simpleStore.setValue(value); - console.log(value); - console.log(simpleStore.getValue()); - assertEq(value, simpleStore.getValue()); - } -} - -interface SimpleStore { - function setValue(uint256) external; - function getValue() external returns (uint256); -} diff --git a/test/mock/DepositContractTestable.sol b/test/mock/DepositContractTestable.sol new file mode 100644 index 0000000..e1a4bff --- /dev/null +++ b/test/mock/DepositContractTestable.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BUSL-1.1 +// SPDX-FileCopyrightText: 2023 Kiln +// +// ██╗ ██╗██╗██╗ ███╗ ██╗ +// ██║ ██╔╝██║██║ ████╗ ██║ +// █████╔╝ ██║██║ ██╔██╗ ██║ +// ██╔═██╗ ██║██║ ██║╚██╗██║ +// ██║ ██╗██║███████╗██║ ╚████║ +// ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ +// +pragma solidity >=0.8.17; + +import "../../src/interface/IDeposit.sol"; + +contract DepositContractTestable is IDeposit { + uint256 constant DEPOSIT_CONTRACT_TREE_DEPTH = 32; + // NOTE: this also ensures `deposit_count` will fit into 64-bits + uint256 constant MAX_DEPOSIT_COUNT = 2 ** DEPOSIT_CONTRACT_TREE_DEPTH - 1; + + bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch; + uint256 public deposit_count; + + bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes; + + bytes32[] public depositDataRoots; + bytes[] public depositPubkeys; + bytes[] public depositWithdrawalCredentials; + bytes[] public depositSignatures; + + constructor() { + // Compute hashes in empty sparse Merkle tree + for (uint256 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) { + bytes32 node; + uint256 size = deposit_count; + for (uint256 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() external view returns (bytes memory) { + return to_little_endian_64(uint64(deposit_count)); + } + + bool dataRootCheck = false; + + function debug_enableDataRootCheck() external { + dataRootCheck = true; + } + + function deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) external payable override { + depositDataRoots.push(deposit_data_root); + depositPubkeys.push(pubkey); + depositWithdrawalCredentials.push(withdrawal_credentials); + depositSignatures.push(signature); + + // Add deposit data root to Merkle tree (update a single `branch` node) + deposit_count += 1; + } + + 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]; + } +} diff --git a/test/utils/BytesGenerator.sol b/test/utils/BytesGenerator.sol new file mode 100644 index 0000000..2e26442 --- /dev/null +++ b/test/utils/BytesGenerator.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: BUSL-1.1 +// SPDX-FileCopyrightText: 2023 Kiln +// +// ██╗ ██╗██╗██╗ ███╗ ██╗ +// ██║ ██╔╝██║██║ ████╗ ██║ +// █████╔╝ ██║██║ ██╔██╗ ██║ +// ██╔═██╗ ██║██║ ██║╚██╗██║ +// ██║ ██╗██║███████╗██║ ╚████║ +// ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ +// +pragma solidity >=0.8.17; + +abstract contract BytesGenerator { + bytes32 salt = bytes32(0); + + function BytesGenerator_slice(bytes memory _bytes, uint256 _start, uint256 _length) + internal + pure + returns (bytes memory) + { + require(_length + 31 >= _length, "slice_overflow"); + require(_bytes.length >= _start + _length, "slice_outOfBounds"); + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + tempBytes := mload(0x40) + let lengthmod := and(_length, 31) + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { 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) + mstore(0x40, and(add(mc, 31), not(31))) + } + default { + tempBytes := mload(0x40) + mstore(tempBytes, 0) + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + + function setSalt(bytes32 _salt) internal { + salt = _salt; + } + + function genBytes(uint256 len) internal returns (bytes memory) { + bytes memory res = ""; + while (res.length < len) { + salt = keccak256(abi.encodePacked(salt)); + if (len - res.length >= 32) { + res = bytes.concat(res, abi.encode(salt)); + } else { + res = bytes.concat(res, BytesGenerator_slice(abi.encode(salt), 0, len - res.length)); + } + } + return res; + } +}