Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jobs:
id: build

- name: Run Forge tests
env:
RPC_URL: ${{ secrets.RPC_URL }}
run: |
forge test -vvv
id: test
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.30"
fs_permissions = [{ access = "read", path = "./test/data" }]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
73 changes: 73 additions & 0 deletions test/TestDepositDataLoader.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.30;

import "forge-std/Test.sol";
import "../src/TwinstakeBatchDepositor.sol";
import {stdJson} from "forge-std/StdJson.sol";

/**
* @title TestDepositDataLoader
* @notice Loads test deposit data from JSON file instead of hardcoding in contract
* @dev This solves the EIP-170 contract size limit issue by moving data external
*/
contract TestDepositDataLoader is Test {
using stdJson for string;

/**
* @notice Struct to match JSON format for parsing
*/
struct DepositDataJson {
uint256 amountWei;
bytes32 depositDataRoot;
bytes pubkey;
bytes signature;
bytes32 withdrawalCredentials;
}

/**
* @notice Load test deposit data from JSON file
* @return Array of test deposits loaded from external JSON file
*/
function getTestDepositData() public returns (TwinstakeBatchDepositor.Deposit[] memory) {
// Read JSON file
string memory root = vm.projectRoot();
string memory path = string.concat(root, "/test/data/test-deposits.json");
string memory json = vm.readFile(path);

bytes memory depositDatajson = json.parseRaw(".deposits");
DepositDataJson[] memory depositDataArray = abi.decode(depositDatajson, (DepositDataJson[]));

// Convert to the contract's Deposit struct format
TwinstakeBatchDepositor.Deposit[] memory deposits =
new TwinstakeBatchDepositor.Deposit[](depositDataArray.length);

for (uint256 i = 0; i < depositDataArray.length; i++) {
deposits[i] = TwinstakeBatchDepositor.Deposit({
pubkey: depositDataArray[i].pubkey,
withdrawalCredentials: bytes.concat(depositDataArray[i].withdrawalCredentials),
signature: depositDataArray[i].signature,
depositDataRoot: depositDataArray[i].depositDataRoot,
amountWei: depositDataArray[i].amountWei
});
}

return deposits;
}

/**
* @notice Get a specific number of test deposits
* @param count Number of deposits to return (max 100)
* @return Array of test deposits with specified count
*/
function getTestDepositData(uint256 count) public returns (TwinstakeBatchDepositor.Deposit[] memory) {
TwinstakeBatchDepositor.Deposit[] memory allDeposits = getTestDepositData();
require(count <= allDeposits.length, "Requested count exceeds available deposits");

TwinstakeBatchDepositor.Deposit[] memory deposits = new TwinstakeBatchDepositor.Deposit[](count);
for (uint256 i = 0; i < count; i++) {
deposits[i] = allDeposits[i];
}

return deposits;
}
}
69 changes: 57 additions & 12 deletions test/TwinstakeBatchDepositorFork.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/TwinstakeBatchDepositor.sol";
import "../src/interfaces/IDepositContract.sol";
import "./TestDepositDataLoader.sol";

contract TwinstakeBatchDepositorForkTest is Test {
TwinstakeBatchDepositor public depositor;
IDepositContract public depositContract;
TestDepositDataLoader public testDepositDataLoader;
address public owner;
address public user;

Expand All @@ -21,12 +23,12 @@ contract TwinstakeBatchDepositorForkTest is Test {
bytes32 constant DEPOSIT_DATA_ROOT = 0x6b73ff7982a0887e0e4f21b141a194cc3b5ea36f1172e59a55a77c901cbda479;

// Second set of deposit data (for 2048 ETH)
bytes constant WITHDRAWAL_CREDENTIALS2 = hex"005454dd98f53ea2b7aa671f6b31e4a5b73797956fbafa2bcf070752bb0395cd";
bytes constant PUBKEY2 =
bytes constant WITHDRAWAL_CREDENTIALS_2048 = hex"005454dd98f53ea2b7aa671f6b31e4a5b73797956fbafa2bcf070752bb0395cd";
bytes constant PUBKEY_2048 =
hex"98cf6a5e3cbc7185afcbc50ab79f22cbb4b6f241e5d9a8c45e2a5bb0a15308b1c19841877eedb6c7fe7e693ab5eeac2c";
bytes constant SIGNATURE2 =
bytes constant SIGNATURE_2048 =
hex"ac4ac3a68c5806976472cd0f89b5ae4c7d0bb6bbdc5d546e5ee11b848bf36f6b8e1a1875af00ba6c17e1e7f70df93ae303cc46c7ca71b6d0ba0af6b7b8de4275cbd3e51335a8f08d268ec494299e1092a9b8707be0a620f94ebec88aed9935a7";
bytes32 constant DEPOSIT_DATA_ROOT2 = 0x1640c484c4872185a884f4c2ac76b2dc8fdec06cbe1297c3e4ddcb6930e9e3a0;
bytes32 constant DEPOSIT_DATA_ROOT_2048 = 0x1640c484c4872185a884f4c2ac76b2dc8fdec06cbe1297c3e4ddcb6930e9e3a0;

function setUp() public {
// Fork mainnet
Expand All @@ -37,6 +39,7 @@ contract TwinstakeBatchDepositorForkTest is Test {

vm.prank(owner);
depositor = new TwinstakeBatchDepositor();
testDepositDataLoader = new TestDepositDataLoader();
}

function test_DefaultDeposit() public {
Expand Down Expand Up @@ -99,10 +102,10 @@ contract TwinstakeBatchDepositorForkTest is Test {
// Create a deposit with maximum amount (2048 ETH)
TwinstakeBatchDepositor.Deposit[] memory deposits = new TwinstakeBatchDepositor.Deposit[](1);
deposits[0] = TwinstakeBatchDepositor.Deposit({
pubkey: PUBKEY2,
withdrawalCredentials: WITHDRAWAL_CREDENTIALS2,
signature: SIGNATURE2,
depositDataRoot: DEPOSIT_DATA_ROOT2,
pubkey: PUBKEY_2048,
withdrawalCredentials: WITHDRAWAL_CREDENTIALS_2048,
signature: SIGNATURE_2048,
depositDataRoot: DEPOSIT_DATA_ROOT_2048,
amountWei: 2048 ether
});

Expand All @@ -124,10 +127,10 @@ contract TwinstakeBatchDepositorForkTest is Test {
});
// Deposit 2: 2048 ETH
TwinstakeBatchDepositor.Deposit memory deposit2 = TwinstakeBatchDepositor.Deposit({
pubkey: PUBKEY2,
withdrawalCredentials: WITHDRAWAL_CREDENTIALS2,
signature: SIGNATURE2,
depositDataRoot: DEPOSIT_DATA_ROOT2,
pubkey: PUBKEY_2048,
withdrawalCredentials: WITHDRAWAL_CREDENTIALS_2048,
signature: SIGNATURE_2048,
depositDataRoot: DEPOSIT_DATA_ROOT_2048,
amountWei: 2048 ether
});
TwinstakeBatchDepositor.Deposit[] memory deposits = new TwinstakeBatchDepositor.Deposit[](2);
Expand All @@ -138,4 +141,46 @@ contract TwinstakeBatchDepositorForkTest is Test {
vm.prank(user);
depositor.batchDeposit{value: totalAmount}(deposits);
}

// Tests for gas usage

function test_3Deposits() public {
TwinstakeBatchDepositor.Deposit[] memory deposits = testDepositDataLoader.getTestDepositData(3);
uint256 totalAmount = 32 ether * 3;
vm.deal(user, totalAmount);
vm.prank(user);
depositor.batchDeposit{value: totalAmount}(deposits);
}

function test_10Deposits() public {
TwinstakeBatchDepositor.Deposit[] memory deposits = testDepositDataLoader.getTestDepositData(10);
uint256 totalAmount = 32 ether * 10;
vm.deal(user, totalAmount);
vm.prank(user);
depositor.batchDeposit{value: totalAmount}(deposits);
}

function test_20Deposits() public {
TwinstakeBatchDepositor.Deposit[] memory deposits = testDepositDataLoader.getTestDepositData(20);
uint256 totalAmount = 32 ether * 20;
vm.deal(user, totalAmount);
vm.prank(user);
depositor.batchDeposit{value: totalAmount}(deposits);
}

function test_50Deposits() public {
TwinstakeBatchDepositor.Deposit[] memory deposits = testDepositDataLoader.getTestDepositData(50);
uint256 totalAmount = 32 ether * 50;
vm.deal(user, totalAmount);
vm.prank(user);
depositor.batchDeposit{value: totalAmount}(deposits);
}

function test_100Deposits() public {
TwinstakeBatchDepositor.Deposit[] memory deposits = testDepositDataLoader.getTestDepositData(100);
uint256 totalAmount = 32 ether * 100;
vm.deal(user, totalAmount);
vm.prank(user);
depositor.batchDeposit{value: totalAmount}(deposits);
}
}
Loading