Skip to content

Commit

Permalink
Merge pull request #10 from lightclient/exit-cleanup
Browse files Browse the repository at this point in the history
all: clean up remaining exit language, fix target var
  • Loading branch information
lightclient authored Apr 8, 2024
2 parents b6a3ffc + 11c6b35 commit 2a216ee
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 84 deletions.
12 changes: 6 additions & 6 deletions src/main.eas
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@

#define SLOT_EXCESS 0
#define SLOT_COUNT 1
#define SLOT_TARGET 2

#define QUEUE_HEAD 2
#define QUEUE_TAIL 3
#define QUEUE_OFFSET 4

#define MIN_FEE 1
#define MAX_REQS 16
#define TARGET_PER_BLOCK 2
#define MAX_PER_BLOCK 16
#define FEE_UPDATE_FRACTION 17

#define INPUT_SIZE 56 ;; the size of (pubkey ++ amount)
Expand Down Expand Up @@ -187,13 +187,13 @@ read_requests:

;; Determine if count is greater than the max withdrawal requests.
dup1 ;; [count, count, head_idx, tail_idx]
push MAX_REQS ;; [reqs_per_block, count, count, head_idx, tail_idx]
push MAX_PER_BLOCK ;; [reqs_per_block, count, count, head_idx, tail_idx]
gt ;; [reqs_per_block > count, count, head_idx, tail_idx]
jumpi @begin_loop ;; [count, head_idx, tail_idx]

;; Discard count, use the max withdrawal requests per block.
pop ;; [head_idx, tail_idx]
push MAX_REQS ;; [count, head_idx, tail_idx]
push MAX_PER_BLOCK ;; [count, head_idx, tail_idx]

begin_loop:
push0 ;; [i, count, head_idx, tail_idx]
Expand Down Expand Up @@ -331,7 +331,7 @@ update_excess:
;; If the sum of the previous excess requests and requests added in the
;; current block is greater than the target, subtract the target from the sum
;; and set it as the new excess withdrawal requests value.
push SLOT_TARGET ;; [target, count, excess, count]
push TARGET_PER_BLOCK ;; [target, count, excess, count]
dup3 ;; [excess, target, count, excess]
dup3 ;; [count, excess, target, count, excess, count]
add ;; [count+excess, target, count, excess, count]
Expand All @@ -346,7 +346,7 @@ update_excess:

compute_excess:
add ;; [count+excess, count]
push SLOT_TARGET
push TARGET_PER_BLOCK ;; [target, count+excess, count]
swap1 ;; [count+excess, target, count]
sub ;; [new_excess, count]

Expand Down
155 changes: 77 additions & 78 deletions test/Contract.t.sol.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ address constant fakeExpo = 0x000000000000000000000000000000000000BbBB;
address constant testCaller = 0x000000000000000000000000000000000000CcCc;
address constant sysaddr = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE;

uint256 constant excess_exits_slot = 0;
uint256 constant exit_count_slot = 1;
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;
uint256 constant target_exits = 2;
uint256 constant max_exits = 16;
uint256 constant target_per_block = 2;
uint256 constant max_per_block = 16;

contract ContractTest is Test {
address unit;
Expand All @@ -32,8 +31,8 @@ contract ContractTest is Test {
assertEq(callFakeExpo(1, 100, 17), 357);
}

// testInvalidExit checks that common invalid exit submissions are rejected.
function testInvalidExit() public {
// testInvalidRequest checks that common invalid withdrawal requests are rejected.
function testInvalidRequest() public {
// pubkey too small
(bool ret,) = addr.call{value: 1e18}(hex"1234");
assertEq(ret, false);
Expand All @@ -47,97 +46,97 @@ contract ContractTest is Test {
assertEq(ret, false);
}

// testExit verifies a single exit below the target exit count is accepted and
// read successfully.
function testExit() public {
bytes memory req = hex"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112222222222222222";
(bool ret,) = addr.call{value: 2}(req);
// testRequest verifies a single withdrawal request below the target request
// count is accepted and read successfully.
function testRequest() public {
bytes memory data = hex"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112222222222222222";
(bool ret,) = addr.call{value: 2}(data);
assertEq(ret, true);
assertStorage(exit_count_slot, 1, "unexpected exit count");
assertStorage(count_slot, 1, "unexpected withdrawal request count");
assertExcess(0);

bytes memory exit = getExits();
assertEq(exit.length, 76);
assertEq(toFixed(exit, 20, 52), toFixed(req, 0, 32));
assertEq(toFixed(exit, 52, 76), toFixed(req, 32, 56));
assertStorage(exit_count_slot, 0, "unexpected exit count");
bytes memory req = getRequests();
assertEq(req.length, 76);
assertEq(toFixed(req, 20, 52), toFixed(data, 0, 32));
assertEq(toFixed(req, 52, 76), toFixed(data, 32, 56));
assertStorage(count_slot, 0, "unexpected request count");
assertExcess(0);
}

// testQueueReset verifies that after a period of time where there are more
// exits than can be read per block, the queue is eventually cleared and the
// 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 exits than the max exits per block (16) so that the queue is not
// Add more requests than the max per block (16) so that the queue is not
// immediately emptied.
for (uint256 i = 0; i < max_exits+1; i++) {
addExit(address(uint160(i)), makeRequest(i), 2);
for (uint256 i = 0; i < max_per_block+1; i++) {
addRequest(address(uint160(i)), makeRequest(i), 2);
}
assertStorage(exit_count_slot, max_exits+1, "unexpected exit count");
assertStorage(count_slot, max_per_block+1, "unexpected request count");

// Simulate syscall, check that max exits per block are read.
checkExits(0, max_exits);
// Simulate syscall, check that max requests per block are read.
checkRequests(0, max_per_block);
assertExcess(15);

// Add another batch of max exits per block (16) so the next read leaves a
// single exit in the queue.
// Add another batch of max requests per block (16) so the next read leaves a
// single request in the queue.
for (uint256 i = 17; i < 33; i++) {
addExit(address(uint160(i)), makeRequest(i), 2);
addRequest(address(uint160(i)), makeRequest(i), 2);
}
assertStorage(exit_count_slot, max_exits, "unexpected exit count");
assertStorage(count_slot, max_per_block, "unexpected request count");

// Simulate syscall. Verify first that max exits per block are read. Then
// verify only the single final exit is read.
checkExits(16, max_exits);
// Simulate syscall. Verify first that max per block are read. Then
// verify only the single final requst is read.
checkRequests(16, max_per_block);
assertExcess(29);
checkExits(32, 1);
checkRequests(32, 1);
assertExcess(27);

// Now ensure the queue is empty and has reset to zero.
assertStorage(queue_head_slot, 0, "expected queue head reset");
assertStorage(queue_tail_slot, 0, "expected queue tail reset");

// Add five (5) more exits to check that new exits can be added after the queue
// 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++) {
addExit(address(uint160(i)), makeRequest(i), 4);
addRequest(address(uint160(i)), makeRequest(i), 4);
}
assertStorage(exit_count_slot, 5, "unexpected exit count");
assertStorage(count_slot, 5, "unexpected request count");

// Simulate syscall, read only the max exits per block.
checkExits(33, 5);
// Simulate syscall, read only the max requests per block.
checkRequests(33, 5);
assertExcess(30);
}


// testExitFee adds many exits, and verifies the exit fee decreases correctly
// until it returns to 0.
function testExitFee() public {
// testFee adds many requests, and verifies the fee decreases correctly until
// it returns to 0.
function testFee() public {
uint256 idx = 0;
uint256 count = max_exits*64;
uint256 count = max_per_block*64;

// Add a bunch of exits.
// Add a bunch of requests.
for (; idx < count; idx++) {
addExit(address(uint160(idx)), makeRequest(idx), 1);
addRequest(address(uint160(idx)), makeRequest(idx), 1);
}
assertStorage(exit_count_slot, count, "unexpected exit count");
checkExits(0, max_exits);
assertStorage(count_slot, count, "unexpected request count");
checkRequests(0, max_per_block);

uint256 read = max_exits;
uint256 excess = count - target_exits;
uint256 read = max_per_block;
uint256 excess = count - target_per_block;

// Attempt to add an expect with fee too low and an exit with fee exactly
// correct. This should cause the excess exits counter to decrease by 1 each
// 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.
for (uint256 i = 0; i < count; i++) {
assertExcess(excess);

uint256 fee = computeExitFee(excess);
addFailedExit(address(uint160(idx)), makeRequest(idx), fee-1);
addExit(address(uint160(idx)), makeRequest(idx), fee);
uint256 fee = computeFee(excess);
addFailedRequest(address(uint160(idx)), makeRequest(idx), fee-1);
addRequest(address(uint160(idx)), makeRequest(idx), fee);

uint256 expected = min(idx-read+1, max_exits);
checkExits(read, expected);
uint256 expected = min(idx-read+1, max_per_block);
checkRequests(read, expected);

if (excess != 0) {
excess--;
Expand All @@ -155,58 +154,58 @@ contract ContractTest is Test {
return y;
}

function addFailedExit(address from, bytes memory req, uint256 value) internal {
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 exit to fail");
assertEq(ret, false, "expected request to fail");
}

// addExit will submit an exit to the system contract with the given values.
function addExit(address from, bytes memory req, uint256 value) internal {
// Load tail index before adding exit.
uint256 exits = load(exit_count_slot);
// addRequest will submit a request to the system contract with the given values.
function addRequest(address from, bytes memory req, uint256 value) internal {
// Load tail index before adding request.
uint256 requests = load(count_slot);
uint256 tail = load(queue_tail_slot);

// Send exit from address.
// Send request from address.
vm.deal(from, value);
vm.prank(from);
(bool ret,) = addr.call{value: value}(req);
assertEq(ret, true, "expected call to succeed");

// Verify the queue data was updated correctly.
assertStorage(exit_count_slot, exits+1, "unexpected exit count");
assertStorage(count_slot, requests+1, "unexpected request count");
assertStorage(queue_tail_slot, tail+1, "unexpected tail slot");

// Verify the exit was written to the queue.
// Verify the request was written to the queue.
uint256 idx = queue_storage_offset+tail*3;
assertStorage(idx, uint256(uint160(from)), "addr not written to queue");
assertStorage(idx+1, toFixed(req, 0, 32), "pk[0:32] not written to queue");
assertStorage(idx+2, toFixed(req, 32, 56), "pk2_am not written to queue");
}

// getExits will simulate a system call to the system contract.
function getExits() internal returns (bytes memory) {
// 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;
}

// checkExits will simulate a system call to the system contract and verify
// the expected exits are returned.
// checkRequest will simulate a system call to the system contract and verify
// the expected requests are returned.
//
// It assumes that addresses are stored as uint256(index) and pubkeys are
// uint8(index), repeating.
function checkExits(uint256 startIndex, uint256 count) internal returns (uint256) {
bytes memory exits = getExits();
assertEq(exits.length, count*76);
function checkRequests(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(exits, offset, offset+20) >> 96, uint256(startIndex+i), "unexpected exit address returned");
assertEq(toFixed(exits, offset+20, offset+52), toFixed(makeRequest(startIndex+i), 0, 32), "unexpected exit pk returned");
assertEq(toFixed(exits, offset+52, offset+68), toFixed(makeRequest(startIndex+i), 32, 48), "unexpected exit pk returned");
assertEq(toFixed(exits, offset+68, offset+76), toFixed(makeRequest(startIndex+i), 48, 56), "unexpected exit amount returned");
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");
}
return count;
}
Expand All @@ -221,9 +220,9 @@ contract ContractTest is Test {
}

function assertExcess(uint256 count) internal {
assertStorage(excess_exits_slot, count, "unexpected excess exits");
assertStorage(excess_slot, count, "unexpected excess requests");
(, bytes memory data) = addr.call("");
assertEq(toFixed(data, 0, 32), count, "unexpected excess exits");
assertEq(toFixed(data, 0, 32), count, "unexpected excess requests");
}

function toFixed(bytes memory data, uint256 start, uint256 end) internal pure returns (uint256) {
Expand All @@ -250,7 +249,7 @@ contract ContractTest is Test {
return out;
}

function computeExitFee(uint256 excess) internal returns (uint256) {
function computeFee(uint256 excess) internal returns (uint256) {
return callFakeExpo(1, int(excess), 17);
}

Expand Down

0 comments on commit 2a216ee

Please sign in to comment.