Skip to content

Commit

Permalink
add test for exit fee
Browse files Browse the repository at this point in the history
  • Loading branch information
lightclient committed Feb 15, 2024
1 parent baad89d commit c604190
Showing 1 changed file with 82 additions and 26 deletions.
108 changes: 82 additions & 26 deletions test/Contract.t.sol.in
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ contract ContractTest is Test {

// testFakeExpo calls the fake exponentiation logic with specific values.
function testFakeExpo() public {
bytes memory data = callFakeExpo(1, 100, 17);
assertEq(data, bytes.concat(bytes32(uint256(357))));
assertEq(callFakeExpo(1, 100, 17), 357);
}

// testInvalidExit checks that common invalid exit submissions are rejected.
Expand All @@ -54,15 +53,15 @@ contract ContractTest is Test {
bytes memory pk = hex"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
(bool ret,) = addr.call{value: 2}(pk);
assertEq(ret, true);
assertStorage(exit_count_slot, 1);
assertStorage(excess_exits_slot, 0);
assertStorage(exit_count_slot, 1, "unexpected exit count");
assertExcess(0);

bytes memory exit = getExits();
assertEq(exit.length, 68);
assertEq(toFixed(exit, 20, 52), toFixed(pk, 0, 32));
assertEq(toFixed(exit, 52, 68), toFixed(pk, 32, 48));
assertStorage(exit_count_slot, 0);
assertStorage(excess_exits_slot, 0);
assertStorage(exit_count_slot, 0, "unexpected exit count");
assertExcess(0);
}

// testQueueReset verifies that after a period of time where there are more
Expand All @@ -74,7 +73,7 @@ contract ContractTest is Test {
for (uint i = 0; i < max_exits+1; i++) {
addExit(address(uint160(i)), makePubkey(i), 2);
}
assertStorage(exit_count_slot, max_exits+1);
assertStorage(exit_count_slot, max_exits+1, "unexpected exit count");

// Simulate syscall, check that max exits per block are read.
checkExits(0, max_exits);
Expand All @@ -85,7 +84,7 @@ contract ContractTest is Test {
for (uint i = 17; i < 33; i++) {
addExit(address(uint160(i)), makePubkey(i), 2);
}
assertStorage(exit_count_slot, max_exits);
assertStorage(exit_count_slot, max_exits, "unexpected exit count");

// Simulate syscall. Verify first that max exits per block are read. Then
// verify only the single final exit is read.
Expand All @@ -95,21 +94,74 @@ contract ContractTest is Test {
assertExcess(27);

// Now ensure the queue is empty and has reset to zero.
assertStorage(queue_head_slot, 0);
assertStorage(queue_tail_slot, 0);
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
// is reset.
for (uint i = 33; i < 38; i++) {
addExit(address(uint160(i)), makePubkey(i), 4);
}
assertStorage(exit_count_slot, 5);
assertStorage(exit_count_slot, 5, "unexpected exit count");

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


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

// Add a bunch of exits.
for (; idx < count; idx++) {
addExit(address(uint160(idx)), makePubkey(idx), 1);
}
assertStorage(exit_count_slot, count, "unexpected exit count");
checkExits(0, max_exits);

uint read = max_exits;
uint excess = count - target_exits;

// 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
// iteration.
for (uint i = 0; i < count; i++) {
assertExcess(excess);

uint fee = computeExitFee(excess);
addFailedExit(address(uint160(idx)), makePubkey(idx), fee-1);
addExit(address(uint160(idx)), makePubkey(idx), fee);

uint expected = min(idx-read+1, max_exits);
checkExits(read, expected);

if (excess != 0) {
excess--;
}
read += expected;
idx++;
}

}

function min(uint x, uint y) internal pure returns (uint) {
if (x < y) {
return x;
}
return y;
}

function addFailedExit(address from, bytes memory pubkey, uint256 value) internal {
vm.deal(from, value);
vm.prank(from);
(bool ret,) = addr.call{value: value}(pubkey);
assertEq(ret, false, "expected exit to fail");
}

// addExit will submit an exit to the system contract with the given values.
function addExit(address from, bytes memory pubkey, uint256 value) internal {
// Load tail index before adding exit.
Expand All @@ -120,17 +172,17 @@ contract ContractTest is Test {
vm.deal(from, value);
vm.prank(from);
(bool ret,) = addr.call{value: value}(pubkey);
assertEq(ret, true);
assertEq(ret, true, "expected call to succeed");

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

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

// getExits will simulate a system call to the system contract.
Expand All @@ -151,9 +203,9 @@ contract ContractTest is Test {
assertEq(exits.length, count*68);
for (uint i = 0; i < count; i++) {
uint offset = i*68;
assertEq(toFixed(exits, offset, offset+20) >> 96, uint256(startIndex+i));
assertEq(toFixed(exits, offset+20, offset+52), toFixed(makePubkey(startIndex+i), 0, 32));
assertEq(toFixed(exits, offset+52, offset+68), toFixed(makePubkey(startIndex+i), 32, 48));
assertEq(toFixed(exits, offset, offset+20) >> 96, uint256(startIndex+i), "unexpected exit value returned");
assertEq(toFixed(exits, offset+20, offset+52), toFixed(makePubkey(startIndex+i), 0, 32), "unexpected exit value returned");
assertEq(toFixed(exits, offset+52, offset+68), toFixed(makePubkey(startIndex+i), 32, 48), "unexpected exit value returned");
}
return count;
}
Expand All @@ -162,15 +214,15 @@ contract ContractTest is Test {
return uint256(vm.load(addr, bytes32(slot)));
}

function assertStorage(uint256 slot, uint256 value) internal {
function assertStorage(uint256 slot, uint256 value, string memory err) internal {
bytes32 got = vm.load(addr, bytes32(slot));
assertEq(got, bytes32(value));
assertEq(got, bytes32(value), err);
}

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

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

function callFakeExpo(int factor, int numerator, int denominator) internal returns (bytes memory) {
function computeExitFee(uint excess) internal returns (uint) {
return callFakeExpo(1, int(excess), 17);
}

function callFakeExpo(int factor, int numerator, int denominator) internal returns (uint) {
(, bytes memory data) = fakeExpo.call(bytes.concat(bytes32(uint256(factor)), bytes32(uint256(numerator)), bytes32(uint256(denominator))));
return data;
return toFixed(data, 0, 32);
}
}

0 comments on commit c604190

Please sign in to comment.