From f03673efbdfb19650492f0923fa67e6816a4587e Mon Sep 17 00:00:00 2001 From: lightclient Date: Fri, 5 Jul 2024 14:50:59 -0600 Subject: [PATCH] all: restructure project to better support multiple system contracts --- build-wrapper | 7 +- src/{ => common}/fake_expo.eas | 0 src/{ => common}/fake_expo_test.eas | 0 .../ctor.eas} | 0 .../main.eas} | 2 +- .../ctor.eas} | 2 +- src/{withdrawal.eas => withdrawals/main.eas} | 2 +- test/Consolidation.t.sol.in | 51 ++++++----- test/FakeExpo.t.sol.in | 4 +- test/Test.sol | 87 +++++++++++++++++++ test/TestHelper.sol | 72 --------------- test/Withdrawal.t.sol.in | 71 ++++++++------- 12 files changed, 155 insertions(+), 143 deletions(-) rename src/{ => common}/fake_expo.eas (100%) rename src/{ => common}/fake_expo_test.eas (100%) rename src/{consolidation_ctor.eas => consolidations/ctor.eas} (100%) rename src/{consolidation.eas => consolidations/main.eas} (99%) rename src/{withdrawal_ctor.eas => withdrawals/ctor.eas} (92%) rename src/{withdrawal.eas => withdrawals/main.eas} (99%) create mode 100644 test/Test.sol delete mode 100644 test/TestHelper.sol diff --git a/build-wrapper b/build-wrapper index 3666458..08fcd13 100755 --- a/build-wrapper +++ b/build-wrapper @@ -3,10 +3,9 @@ set -euf -o pipefail SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )"; -WITHDRAWAL_BYTECODE="$(geas "src/withdrawal.eas")" -FAKE_EXPO_BYTECODE="$(geas "src/fake_expo_test.eas")" - -CONSOLODATION_BYTECODE="$(geas "src/consolidation.eas")" +WITHDRAWAL_BYTECODE="$(geas "src/withdrawals/main.eas")" +CONSOLODATION_BYTECODE="$(geas "src/consolidations/main.eas")" +FAKE_EXPO_BYTECODE="$(geas "src/common/fake_expo_test.eas")" sed \ -e "s/@bytecode@/$WITHDRAWAL_BYTECODE/" \ diff --git a/src/fake_expo.eas b/src/common/fake_expo.eas similarity index 100% rename from src/fake_expo.eas rename to src/common/fake_expo.eas diff --git a/src/fake_expo_test.eas b/src/common/fake_expo_test.eas similarity index 100% rename from src/fake_expo_test.eas rename to src/common/fake_expo_test.eas diff --git a/src/consolidation_ctor.eas b/src/consolidations/ctor.eas similarity index 100% rename from src/consolidation_ctor.eas rename to src/consolidations/ctor.eas diff --git a/src/consolidation.eas b/src/consolidations/main.eas similarity index 99% rename from src/consolidation.eas rename to src/consolidations/main.eas index 35011dc..6ef34d4 100644 --- a/src/consolidation.eas +++ b/src/consolidations/main.eas @@ -85,7 +85,7 @@ check_input: push SLOT_EXCESS ;; [excess_slot, update_fraction] sload ;; [excess, update_fraction] push MIN_FEE ;; [min_fee, excess, update_fraction] - #include "fake_expo.eas" + #include "../common/fake_expo.eas" ;; Determine if the fee provided is enough to cover the request fee. callvalue ;; [callvalue, req_fee] diff --git a/src/withdrawal_ctor.eas b/src/withdrawals/ctor.eas similarity index 92% rename from src/withdrawal_ctor.eas rename to src/withdrawals/ctor.eas index 3e7fa7f..76e5f4f 100644 --- a/src/withdrawal_ctor.eas +++ b/src/withdrawals/ctor.eas @@ -15,5 +15,5 @@ push0 return .start: -#assemble "withdrawal.eas" +#assemble "main.eas" .end: diff --git a/src/withdrawal.eas b/src/withdrawals/main.eas similarity index 99% rename from src/withdrawal.eas rename to src/withdrawals/main.eas index 6c38395..a2d3ec5 100644 --- a/src/withdrawal.eas +++ b/src/withdrawals/main.eas @@ -95,7 +95,7 @@ check_input: push SLOT_EXCESS ;; [excess_slot, update_fraction] sload ;; [excess, update_fraction] push MIN_FEE ;; [min_fee, excess, update_fraction] - #include "fake_expo.eas" + #include "../common/fake_expo.eas" ;; Determine if the fee provided is enough to cover the withdrawal request fee. callvalue ;; [callvalue, req_fee] diff --git a/test/Consolidation.t.sol.in b/test/Consolidation.t.sol.in index 6740881..1ae1d3e 100644 --- a/test/Consolidation.t.sol.in +++ b/test/Consolidation.t.sol.in @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "./TestHelper.sol"; +import "./Test.sol"; uint256 constant target_per_block = 1; uint256 constant max_per_block = 1; -contract ConsolidationTest is TestHelper { +contract ConsolidationTest is Test { function setUp() public { vm.etch(addr, hex"@bytecode@"); @@ -28,9 +28,9 @@ contract ConsolidationTest is TestHelper { assertEq(ret, false); } - // testRequest verifies a single request below the target request - // count is accepted and read successfully. - function testRequest() public { + // testConsolidation verifies a single consolidation request below the target + // request count is accepted and read successfully. + function testConsolidation() public { bytes memory data = hex"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222"; (bool ret,) = addr.call{value: 2}(data); assertEq(ret, true); @@ -53,26 +53,26 @@ contract ConsolidationTest is TestHelper { // Add more requests than the max per block (1) so that the queue is not // immediately emptied. for (uint256 i = 0; i < max_per_block+1; i++) { - addRequest(address(uint160(i)), makeRequest(i), 2); + addRequest(address(uint160(i)), makeConsolidation(i), 2); } assertStorage(count_slot, max_per_block+1, "unexpected request count"); // Simulate syscall, check that max requests per block are read. - checkRequests(0, max_per_block); + checkConsolidations(0, max_per_block); assertExcess(1); // Add another batch of max requests per block (1) so the next read leaves a // single request in the queue. for (uint256 i = 2; i < 3; i++) { - addRequest(address(uint160(i)), makeRequest(i), 2); + addRequest(address(uint160(i)), makeConsolidation(i), 2); } assertStorage(count_slot, max_per_block, "unexpected request count"); // Simulate syscall. Verify first that max per block are read. Then // verify only the single final requst is read. - checkRequests(1, max_per_block); + checkConsolidations(1, max_per_block); assertExcess(1); - checkRequests(2, 1); + checkConsolidations(2, 1); assertExcess(0); // Now ensure the queue is empty and has reset to zero. @@ -82,12 +82,12 @@ contract ConsolidationTest is TestHelper { // Add five (5) more requests to check that new requests can be added after the queue // is reset. for (uint256 i = 3; i < 8; i++) { - addRequest(address(uint160(i)), makeRequest(i), 4); + addRequest(address(uint160(i)), makeConsolidation(i), 4); } assertStorage(count_slot, 5, "unexpected request count"); // Simulate syscall, read only the max requests per block. - checkRequests(3, 1); + checkConsolidations(3, 1); assertExcess(4); } @@ -99,10 +99,10 @@ contract ConsolidationTest is TestHelper { // Add a bunch of requests. for (; idx < count; idx++) { - addRequest(address(uint160(idx)), makeRequest(idx), 1); + addRequest(address(uint160(idx)), makeConsolidation(idx), 1); } assertStorage(count_slot, count, "unexpected request count"); - checkRequests(0, max_per_block); + checkConsolidations(0, max_per_block); uint256 read = max_per_block; uint256 excess = count - target_per_block; @@ -115,13 +115,13 @@ contract ConsolidationTest is TestHelper { uint256 fee = computeFee(excess); if (idx % 2 == 0) { - addRequest(address(uint160(idx)), makeRequest(idx), fee); + addRequest(address(uint160(idx)), makeConsolidation(idx), fee); } else { - addFailedRequest(address(uint160(idx)), makeRequest(idx), fee-1); + addFailedRequest(address(uint160(idx)), makeConsolidation(idx), fee-1); } uint256 expected = min(idx-read+1, max_per_block); - checkRequests(read, expected); + checkConsolidations(read, expected); if (excess > 0 && idx % 2 != 0) { excess--; @@ -174,27 +174,27 @@ contract ConsolidationTest is TestHelper { assertStorage(idx+3, toFixed(req, 64, 96), "target[16:48] not written to queue"); } - // checkRequest will simulate a system call to the system contract and verify - // the expected requests are returned. + // checkConsolidations will simulate a system call to the system contract and + // verify the expected consolidation requests are returned. // // It assumes that addresses are stored as uint256(index) and pubkeys are // uint8(index), repeating. - function checkRequests(uint256 startIndex, uint256 count) internal returns (uint256) { + function checkConsolidations(uint256 startIndex, uint256 count) internal returns (uint256) { bytes memory requests = getRequests(); assertEq(requests.length, count*116); for (uint256 i = 0; i < count; i++) { uint256 offset = i*116; assertEq(toFixed(requests, offset, offset+20) >> 96, uint256(startIndex+i), "unexpected request address returned"); - assertEq(toFixed(requests, offset+20, offset+52), toFixed(makeRequest(startIndex+i), 0, 32), "unexpected source[0:32] returned"); - assertEq(toFixed(requests, offset+52, offset+84), toFixed(makeRequest(startIndex+i), 32, 64), "unexpected source[32:48] ++ target[0:16] returned"); - assertEq(toFixed(requests, offset+84, offset+116), toFixed(makeRequest(startIndex+i), 64, 96), "unexpected target[16:48] returned"); + assertEq(toFixed(requests, offset+20, offset+52), toFixed(makeConsolidation(startIndex+i), 0, 32), "unexpected source[0:32] returned"); + assertEq(toFixed(requests, offset+52, offset+84), toFixed(makeConsolidation(startIndex+i), 32, 64), "unexpected source[32:48] ++ target[0:16] returned"); + assertEq(toFixed(requests, offset+84, offset+116), toFixed(makeConsolidation(startIndex+i), 64, 96), "unexpected target[16:48] returned"); } return count; } - function makeRequest(uint256 x) internal pure returns (bytes memory) { + // makeWithdrawal constructs a withdrawal request with a base of x. + function makeConsolidation(uint256 x) internal pure returns (bytes memory) { bytes memory out = new bytes(96); - // source for (uint256 i = 0; i < 48; i++) { out[i] = bytes1(uint8(x)); @@ -203,7 +203,6 @@ contract ConsolidationTest is TestHelper { for (uint256 i = 0; i < 48; i++) { out[48 + i] = bytes1(uint8(x+1)); } - return out; } } diff --git a/test/FakeExpo.t.sol.in b/test/FakeExpo.t.sol.in index 2a94b1e..2a12229 100644 --- a/test/FakeExpo.t.sol.in +++ b/test/FakeExpo.t.sol.in @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "./TestHelper.sol"; +import "./Test.sol"; -contract FakeExpoTest is TestHelper { +contract FakeExpoTest is Test { function setUp() public { vm.etch(fakeExpo, hex"@bytecode@"); } diff --git a/test/Test.sol b/test/Test.sol new file mode 100644 index 0000000..d1c8d9e --- /dev/null +++ b/test/Test.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test as StdTest} from "forge-std/Test.sol"; + +address constant fakeExpo = 0x000000000000000000000000000000000000BbBB; +address constant addr = 0x000000000000000000000000000000000000aaaa; +address constant sysaddr = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE; + +uint256 constant excess_slot = 0; +uint256 constant count_slot = 1; +uint256 constant queue_head_slot = 2; +uint256 constant queue_tail_slot = 3; +uint256 constant queue_storage_offset = 4; + +// Test has some helper functions used by multiple system contract test suites. +abstract contract Test is StdTest { + // getRequests makes a call to the system contract as the system address in + // order to trigger a dequeue action. + function getRequests() internal returns (bytes memory) { + vm.prank(sysaddr); + (bool ret, bytes memory data) = addr.call(""); + assertEq(ret, true); + return data; + } + + // addFailedRequest submits a request to the system contract and expects it to + // fail. + function addFailedRequest(address from, bytes memory req, uint256 value) internal { + vm.deal(from, value); + vm.prank(from); + (bool ret,) = addr.call{value: value}(req); + assertEq(ret, false, "expected request to fail"); + } + + // load is a helper function to read a specific storage slot in the system + // contract. + function load(uint256 slot) internal view returns (uint256) { + return uint256(vm.load(addr, bytes32(slot))); + } + + // assertStorage reads a value from the system contract and asserts it is + // equal to the provided value. + function assertStorage(uint256 slot, uint256 value, string memory err) internal { + bytes32 got = vm.load(addr, bytes32(slot)); + assertEq(got, bytes32(value), err); + } + + // assertExcess verifies the excess returned from storage and by calling the + // system contract matches count. + function assertExcess(uint256 count) internal { + assertStorage(excess_slot, count, "unexpected excess requests"); + (, bytes memory data) = addr.call(""); + assertEq(toFixed(data, 0, 32), count, "unexpected excess requests"); + } +} + +// min returns the minimum value of x and y. +function min(uint256 x, uint256 y) pure returns (uint256) { + if (x < y) { + return x; + } + return y; +} + +// toFixed copys data from memory into a uint256. If the length is less than 32, +// the output is right-padded with zeros. +function toFixed(bytes memory data, uint256 start, uint256 end) pure returns (uint256) { + require(end-start <= 32, "range cannot be larger than 32 bytes"); + bytes memory out = new bytes(32); + for (uint256 i = start; i < end; i++) { + out[i-start] = data[i]; + } + return uint256(bytes32(out)); +} + +// computeFee calls the fake exponentiation contract with the specified +// parameters to determine the correctt fee value. +function computeFee(uint256 excess) returns (uint256) { + return callFakeExpo(1, int(excess), 17); +} + +// callFakeExpo makes a raw call to the fake exponentiation contract. +function callFakeExpo(int factor, int numerator, int denominator) returns (uint256) { + (, bytes memory data) = fakeExpo.call(bytes.concat(bytes32(uint256(factor)), bytes32(uint256(numerator)), bytes32(uint256(denominator)))); + return toFixed(data, 0, 32); +} diff --git a/test/TestHelper.sol b/test/TestHelper.sol deleted file mode 100644 index a4cd466..0000000 --- a/test/TestHelper.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; - -address constant fakeExpo = 0x000000000000000000000000000000000000BbBB; -address constant addr = 0x000000000000000000000000000000000000aaaa; -address constant sysaddr = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE; - -uint256 constant excess_slot = 0; -uint256 constant count_slot = 1; -uint256 constant queue_head_slot = 2; -uint256 constant queue_tail_slot = 3; -uint256 constant queue_storage_offset = 4; - -abstract contract TestHelper is Test { - - function min(uint256 x, uint256 y) internal pure returns (uint256) { - if (x < y) { - return x; - } - return y; - } - - function addFailedRequest(address from, bytes memory req, uint256 value) internal { - vm.deal(from, value); - vm.prank(from); - (bool ret,) = addr.call{value: value}(req); - assertEq(ret, false, "expected request to fail"); - } - - // getRequests will simulate a system call to the system contract. - function getRequests() internal returns (bytes memory) { - vm.prank(sysaddr); - (bool ret, bytes memory data) = addr.call(""); - assertEq(ret, true); - return data; - } - - function load(uint256 slot) internal view returns (uint256) { - return uint256(vm.load(addr, bytes32(slot))); - } - - function assertStorage(uint256 slot, uint256 value, string memory err) internal { - bytes32 got = vm.load(addr, bytes32(slot)); - assertEq(got, bytes32(value), err); - } - - function assertExcess(uint256 count) internal { - assertStorage(excess_slot, count, "unexpected excess requests"); - (, bytes memory data) = addr.call(""); - assertEq(toFixed(data, 0, 32), count, "unexpected excess requests"); - } - - function toFixed(bytes memory data, uint256 start, uint256 end) internal pure returns (uint256) { - require(end-start <= 32, "range cannot be larger than 32 bytes"); - bytes memory out = new bytes(32); - for (uint256 i = start; i < end; i++) { - out[i-start] = data[i]; - } - return uint256(bytes32(out)); - } - - function computeFee(uint256 excess) internal returns (uint256) { - return callFakeExpo(1, int(excess), 17); - } - - function callFakeExpo(int factor, int numerator, int denominator) internal returns (uint256) { - (, bytes memory data) = fakeExpo.call(bytes.concat(bytes32(uint256(factor)), bytes32(uint256(numerator)), bytes32(uint256(denominator)))); - return toFixed(data, 0, 32); - } -} diff --git a/test/Withdrawal.t.sol.in b/test/Withdrawal.t.sol.in index 3d4bb31..b0d5959 100644 --- a/test/Withdrawal.t.sol.in +++ b/test/Withdrawal.t.sol.in @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "./TestHelper.sol"; +import "./Test.sol"; uint256 constant target_per_block = 2; uint256 constant max_per_block = 16; -contract WithdrawalTest is TestHelper { +contract WithdrawalsTest is Test { address unit; function setUp() public { @@ -15,8 +15,8 @@ contract WithdrawalTest is TestHelper { unit = addr; } - // testInvalidRequest checks that common invalid withdrawal requests are rejected. - function testInvalidRequest() public { + // testInvalidWithdrawal checks that common invalid withdrawal requests are rejected. + function testInvalidWithdrawal() public { // pubkey too small (bool ret,) = addr.call{value: 1e18}(hex"1234"); assertEq(ret, false); @@ -30,13 +30,13 @@ contract WithdrawalTest is TestHelper { assertEq(ret, false); } - // testRequest verifies a single withdrawal request below the target request + // testWithdrawal verifies a single withdrawal request below the target request // count is accepted and read successfully. - function testRequest() public { + function testWithdrawal() public { bytes memory data = hex"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112222222222222222"; (bool ret,) = addr.call{value: 2}(data); assertEq(ret, true); - assertStorage(count_slot, 1, "unexpected withdrawal request count"); + assertStorage(count_slot, 1, "unexpected request count"); assertExcess(0); bytes memory req = getRequests(); @@ -51,29 +51,29 @@ contract WithdrawalTest is TestHelper { // request than can be read per block, the queue is eventually cleared and the // head and tails are reset to zero. function testQueueReset() public { - // Add more requests than the max per block (16) so that the queue is not - // immediately emptied. + // Add more withdrawal requests than the max per block (16) so that the + // queue is not immediately emptied. for (uint256 i = 0; i < max_per_block+1; i++) { - addRequest(address(uint160(i)), makeRequest(i), 2); + addRequest(address(uint160(i)), makeWithdrawal(i), 2); } assertStorage(count_slot, max_per_block+1, "unexpected request count"); - // Simulate syscall, check that max requests per block are read. - checkRequests(0, max_per_block); + // Simulate syscall, check that max withdrawal requests per block are read. + checkWithdrawals(0, max_per_block); assertExcess(15); - // Add another batch of max requests per block (16) so the next read leaves a - // single request in the queue. + // Add another batch of max withdrawal requests per block (16) so the next + // read leaves a single withdrawal request in the queue. for (uint256 i = 17; i < 33; i++) { - addRequest(address(uint160(i)), makeRequest(i), 2); + addRequest(address(uint160(i)), makeWithdrawal(i), 2); } assertStorage(count_slot, max_per_block, "unexpected request count"); // Simulate syscall. Verify first that max per block are read. Then // verify only the single final requst is read. - checkRequests(16, max_per_block); + checkWithdrawals(16, max_per_block); assertExcess(29); - checkRequests(32, 1); + checkWithdrawals(32, 1); assertExcess(27); // Now ensure the queue is empty and has reset to zero. @@ -83,12 +83,12 @@ contract WithdrawalTest is TestHelper { // Add five (5) more requests to check that new requests can be added after the queue // is reset. for (uint256 i = 33; i < 38; i++) { - addRequest(address(uint160(i)), makeRequest(i), 4); + addRequest(address(uint160(i)), makeWithdrawal(i), 4); } assertStorage(count_slot, 5, "unexpected request count"); // Simulate syscall, read only the max requests per block. - checkRequests(33, 5); + checkWithdrawals(33, 5); assertExcess(30); } @@ -101,26 +101,26 @@ contract WithdrawalTest is TestHelper { // Add a bunch of requests. for (; idx < count; idx++) { - addRequest(address(uint160(idx)), makeRequest(idx), 1); + addRequest(address(uint160(idx)), makeWithdrawal(idx), 1); } assertStorage(count_slot, count, "unexpected request count"); - checkRequests(0, max_per_block); + checkWithdrawals(0, max_per_block); uint256 read = max_per_block; uint256 excess = count - target_per_block; - // Attempt to add a request with fee too low and a request with fee exactly - // correct. This should cause the excess requests counter to decrease by 1 each - // iteration. + // Attempt to add a withdrawal request with fee too low and a withdrawal + // request with fee exactly correct. This should cause the excess requests + // counter to decrease by 1 each iteration. for (uint256 i = 0; i < count; i++) { assertExcess(excess); uint256 fee = computeFee(excess); - addFailedRequest(address(uint160(idx)), makeRequest(idx), fee-1); - addRequest(address(uint160(idx)), makeRequest(idx), fee); + addFailedRequest(address(uint160(idx)), makeWithdrawal(idx), fee-1); + addRequest(address(uint160(idx)), makeWithdrawal(idx), fee); uint256 expected = min(idx-read+1, max_per_block); - checkRequests(read, expected); + checkWithdrawals(read, expected); if (excess != 0) { excess--; @@ -172,27 +172,27 @@ contract WithdrawalTest is TestHelper { assertStorage(idx+2, toFixed(req, 32, 56), "pk2_am not written to queue"); } - // checkRequest will simulate a system call to the system contract and verify - // the expected requests are returned. + // checkWithdrawals will simulate a system call to the system contract and verify + // the expected withdrawal requests are returned. // // It assumes that addresses are stored as uint256(index) and pubkeys are // uint8(index), repeating. - function checkRequests(uint256 startIndex, uint256 count) internal returns (uint256) { + function checkWithdrawals(uint256 startIndex, uint256 count) internal returns (uint256) { bytes memory requests = getRequests(); assertEq(requests.length, count*76); for (uint256 i = 0; i < count; i++) { uint256 offset = i*76; assertEq(toFixed(requests, offset, offset+20) >> 96, uint256(startIndex+i), "unexpected request address returned"); - assertEq(toFixed(requests, offset+20, offset+52), toFixed(makeRequest(startIndex+i), 0, 32), "unexpected request pk returned"); - assertEq(toFixed(requests, offset+52, offset+68), toFixed(makeRequest(startIndex+i), 32, 48), "unexpected request pk returned"); - assertEq(toFixed(requests, offset+68, offset+76), toFixed(makeRequest(startIndex+i), 48, 56), "unexpected request amount returned"); + assertEq(toFixed(requests, offset+20, offset+52), toFixed(makeWithdrawal(startIndex+i), 0, 32), "unexpected request pk returned"); + assertEq(toFixed(requests, offset+52, offset+68), toFixed(makeWithdrawal(startIndex+i), 32, 48), "unexpected request pk returned"); + assertEq(toFixed(requests, offset+68, offset+76), toFixed(makeWithdrawal(startIndex+i), 48, 56), "unexpected request amount returned"); } return count; } - function makeRequest(uint256 x) internal pure returns (bytes memory) { + // makeWithdrawal constructs a withdrawal request with a base of x. + function makeWithdrawal(uint256 x) internal pure returns (bytes memory) { bytes memory out = new bytes(56); - // pubkey for (uint256 i = 0; i < 48; i++) { out[i] = bytes1(uint8(x)); @@ -201,7 +201,6 @@ contract WithdrawalTest is TestHelper { for (uint256 i = 0; i < 8; i++) { out[48 + i] = bytes1(uint8(x+1)); } - return out; } }