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: 1 addition & 1 deletion bytecode/builder_exits/ctor.hex
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5f5561018c80602d5f395ff33373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461018857600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603014608857366101885734610188575f5260205ff35b341061018857600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101175782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160e1565b91018092146101295790600255610134565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561016157505f5b6001546002828201116101765750505f61017c565b01600290035b5f555f6001556044025ff35b5f5ffd
7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5f556101ca80602d5f395ff33373fffffffffffffffffffffffffffffffffffffffe1460e1575f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101c65760015460028111605157506057565b60029003015b601190600182026001905f5b5f821115607e57810190830284830290049160010191906063565b909390049250505036603014609e57366101c657346101c6575f5260205ff35b34106101c657600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160f5575060105b5f5b81811461012d5782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160f7565b910180921461013f579060025561014a565b90505f6002555f6003555b36610198575f54600154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101865760028282011161018e575b50505f6101ba565b01600290036101ba565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5f555f6001556044025ff35b5f5ffd
2 changes: 1 addition & 1 deletion bytecode/builder_exits/main.hex
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461018857600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603014608857366101885734610188575f5260205ff35b341061018857600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101175782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160e1565b91018092146101295790600255610134565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561016157505f5b6001546002828201116101765750505f61017c565b01600290035b5f555f6001556044025ff35b5f5ffd
3373fffffffffffffffffffffffffffffffffffffffe1460e1575f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101c65760015460028111605157506057565b60029003015b601190600182026001905f5b5f821115607e57810190830284830290049160010191906063565b909390049250505036603014609e57366101c657346101c6575f5260205ff35b34106101c657600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160f5575060105b5f5b81811461012d5782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160f7565b910180921461013f579060025561014a565b90505f6002555f6003555b36610198575f54600154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101865760028282011161018e575b50505f6101ba565b01600290036101ba565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5f555f6001556044025ff35b5f5ffd
106 changes: 67 additions & 39 deletions src/builder_exits/main.eas
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@
;;; CONSTANTS ------------------------------------------------------------------
;;; ----------------------------------------------------------------------------

#define SYSTEM_ADDR = .address(0xfffffffffffffffffffffffffffffffffffffffe)
#define SYSTEM_ADDR = address(0xfffffffffffffffffffffffffffffffffffffffe)

#define SLOT_EXCESS = 0
#define SLOT_COUNT = 1
#define SLOT_EXCESS = 0 ; slot: stores the count of excess requests
#define SLOT_COUNT = 1 ; slot: stores count of requests added in current block
#define QUEUE_HEAD = 2 ; slot: stores head index of queue
#define QUEUE_TAIL = 3 ; slot: stores tail index of queue

#define QUEUE_HEAD = 2
#define QUEUE_TAIL = 3
#define QUEUE_OFFSET = 4
#define QUEUE_OFFSET = 4 ; count of storage slots. beyond this, queue data is stored

;;; This value is stored into SLOT_EXCESS to disable input into the queue.
#define INHIBITOR = (1 << 256) - 1

;;; Fee parameters.
#define MIN_FEE = 1
#define TARGET_PER_BLOCK = 2
#define MAX_PER_BLOCK = 16
#define FEE_UPDATE_FRACTION = 17
#define EXCESS_INHIBITOR = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

#define INPUT_SIZE = 48 ; the size of (pubkey)
#define RECORD_SIZE = 68 ; the size of (address ++ pubkey)
Expand All @@ -53,17 +56,40 @@
;;; USER SUBROUTINE ----------------------------------------------------------
;;; --------------------------------------------------------------------------

;; Compute the fee using fake expo and the current excess exit requests.
;; Compute the current excess requests.
push SLOT_EXCESS ; [excess_slot]
sload ; [excess]

;; Stop if inhibitor is set.
dup1 ; [excess, excess]
push INHIBITOR ; [inhibit, excess, excess]
eq ; [excess==inhibit, excess]
jumpi @revert ; [excess]

;; Now ensure the excess value also includes any requests added in the
;; current block. To do this, we check if this block already had more than
;; TARGET_PER_BLOCK requests and add the difference onto the long-term excess
;; value.
push SLOT_COUNT ; [count_slot, excess]
sload ; [count, excess]
push TARGET_PER_BLOCK ; [target, count, excess]
dup2 ; [count, target, count, excess]
gt ; [count>target, count, excess]
jumpi @bump_excess ; [count, excess]

pop ; [excess]
jump @compute_user_fee

bump_excess: ; [count, excess]
push TARGET_PER_BLOCK ; [target, count, excess]
swap1 ; [count, target, excess]
sub ; [count-target, excess]
add ; [excess=excess+count-target]

compute_user_fee:
;; Compute the fee using the fake exponential.
push FEE_UPDATE_FRACTION
push SLOT_EXCESS ; [excess_slot, update_fraction]
sload ; [excess, update_fraction]

;; Check if the pre-fork inhibitor is still active, revert if so.
dup1 ; [excess, excess, update_fraction]
push EXCESS_INHIBITOR ; [inhibitor, excess, excess, update_fraction]
eq ; [inhibitor == excess, excess, update_fraction]
jumpi @revert ; [excess, update_fraction]

swap1 ; [excess, update_fraction]
push MIN_FEE ; [min_fee, excess, update_fraction]
#include "../common/fake_expo.eas"

Expand Down Expand Up @@ -338,37 +364,35 @@ reset_queue:
sstore ; [count]

update_excess:
;; Update the new excess exit requests.
;; If the system call receives any input, disable the queue by storing INHIBITOR
;; into the excess slot.
calldatasize ; [calldatasize, count]
jumpi @set_inhibitor ; [count]

;; Load current excess and added-in-block counter values.
push SLOT_EXCESS ; [excess_slot, count]
sload ; [excess, count]

;; Check if excess needs to be reset to 0 for first iteration after
;; activation.
dup1 ; [excess, excess, count]
push EXCESS_INHIBITOR ; [inhibitor, excess, excess, count]
eq ; [inhibitor == excess, excess, count]
iszero ; [inhibitor != excess, excess, count]
jumpi @skip_reset ; [excess, count]

;; Drop the excess from stack and use 0.
pop ; [count]
push 0 ; [reset_excess, count]

skip_reset:
push SLOT_COUNT ; [count_slot, excess, count]
sload ; [count, excess, count]
sload ; [added_count, excess, count]

;; If inhibitor is set, restore excess to zero, i.e. re-enable the queue.
dup2 ; [excess, added_count, excess, count]
push INHIBITOR ; [inhibitor, excess, added_count, excess, count]
eq ; [excess==inhibitor, added_count, excess, count]
jumpi @zero_excess ; [added_count, excess, count]

;; 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 requests value.
push TARGET_PER_BLOCK ; [target, count, excess, count]
dup3 ; [excess, target, count, excess, count]
dup3 ; [count, excess, target, count, excess, count]
add ; [count+excess, target, count, excess, count]
gt ; [count+excess > target, count, excess, count]
jumpi @compute_excess ; [count, excess, count]
push TARGET_PER_BLOCK ; [target, added_count, excess, count]
dup3 ; [excess, target, added_count, excess, count]
dup3 ; [added_count, excess, target, added_count, excess, count]
add ; [added_count+excess, target, added_count, excess, count]
gt ; [added_count+excess > target, added_count, excess, count]
jumpi @compute_excess ; [added_count, excess, count]

;; Zero out excess.
zero_excess:
pop ; [excess, count]
pop ; [count]
push 0 ; [0, count]
Expand All @@ -379,6 +403,10 @@ compute_excess:
push TARGET_PER_BLOCK ; [target, count+excess, count]
swap1 ; [count+excess, target, count]
sub ; [new_excess, count]
jump @store_excess

set_inhibitor: ; [count]
push INHIBITOR ; [new_excess, count]

store_excess:
push SLOT_EXCESS ; [excess_slot, new_excess, count]
Expand Down
123 changes: 103 additions & 20 deletions test/BuilderExit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ contract BuilderExitTest is Test {
// Add more exit 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)), makeExit(i), 2);
uint256 fee = getCurrentFee();
addRequest(address(uint160(i)), makeExit(i), fee);
}
assertStorage(count_slot, max_per_block+1, "unexpected request count");

Expand All @@ -102,7 +103,8 @@ contract BuilderExitTest is Test {
// Add another batch of max exit requests per block (16) so the next read
// leaves a single exit request in the queue.
for (uint256 i = 17; i < 33; i++) {
addRequest(address(uint160(i)), makeExit(i), 2);
uint256 fee = getCurrentFee();
addRequest(address(uint160(i)), makeExit(i), fee);
}
assertStorage(count_slot, max_per_block, "unexpected request count");

Expand All @@ -120,7 +122,8 @@ contract BuilderExitTest is Test {
// 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)), makeExit(i), 4);
uint256 fee = getCurrentFee();
addRequest(address(uint160(i)), makeExit(i), fee);
}
assertStorage(count_slot, 5, "unexpected request count");

Expand All @@ -137,18 +140,24 @@ contract BuilderExitTest is Test {

// Add a bunch of requests.
for (; idx < count; idx++) {
addRequest(address(uint160(idx)), makeExit(idx), 1);
uint256 fee = getCurrentFee();
if (idx < target_per_block) {
assertEq(fee, 1, "unexpected fee for request below excess");
} else {
assertEq(fee, computeFee(idx - target_per_block), "unexpected fee");
}
addRequest(address(uint160(idx)), makeExit(idx), fee);
}
assertStorage(count_slot, count, "unexpected request count");
checkExits(0, max_per_block);

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

// Attempt to add an exit request with fee too low and an exit 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++) {
// Attempt to add a deposit request one wei short of the stake plus fee and a
// deposit request with exactly stake plus fee. This should cause the excess
// requests counter to decrease until it returns to 0.
while (excess != 0) {
assertExcess(excess);

uint256 fee = computeFee(excess);
Expand All @@ -158,14 +167,41 @@ contract BuilderExitTest is Test {
uint256 expected = min(idx-read+1, max_per_block);
checkExits(read, expected);

if (excess != 0) {
excess--;
if (excess + 1 > target_per_block) {
excess = excess + 1 - target_per_block;
} else {
excess = 0;
}
read += expected;
idx++;
}
}

// testFeePerTx checks how fees are computed within a single block.
function testFeePerTx() public {
// first requests have a fee of 1
uint256 idx = 0;
for (; idx <= target_per_block+12; idx++) {
addRequest(address(uint160(idx)), makeExit(idx), 1);
}
assertStorage(count_slot, idx, "unexpected request count in storage");

// now fee rises. Here we just run it until the fee exceeds 100 gwei.
uint256 prevFee = 1;
while (true) {
uint256 fee = getCurrentFee();
if (fee >= 100 gwei) {
break;
}
assertGe(fee, prevFee, "fee did not rise");
addRequest(address(uint160(idx)), makeExit(idx), fee);
idx++;
}

assertEq(idx, 433, "unexpected request count");
assertStorage(count_slot, idx, "unexpected request count in storage");
}

// testFeeGetterRejectsValue verifies the empty-calldata fee getter reverts
// when value is attached, preventing accidentally lost funds.
function testFeeGetterRejectsValue() public {
Expand All @@ -174,21 +210,61 @@ contract BuilderExitTest is Test {
assertEq(ret, false, "fee getter must reject callvalue");
}

// testInhibitorReset verifies that after the first system call the excess
// value is reset to 0.
function testInhibitorReset() public {
vm.store(addr, bytes32(0), bytes32(inhibitor));
// testSystemCallWithInput verifies that a system call with input drains the queue, and
// sets the inhibitor to prevent further additions.
function testSystemCallWithInput() public {
addRequest(address(this), makeExit(1), 1);

// Disable the queue with a system call that carries input data.
vm.prank(sysaddr);
(bool ret, bytes memory data) = addr.call("");
assertStorage(excess_slot, 0, "expected excess requests to be reset");
(bool ret, bytes memory data) = addr.call(hex"01");
assertEq(ret, true);
assertEq(data.length, 68, "system call should drain the queue");
assertStorage(excess_slot, inhibitor, "expected inhibitor in excess storage slot");

// Check that requesting the current fee fails.
(ret,) = addr.staticcall("");
assertEq(ret, false, "expected fee getter to fail");

vm.store(addr, bytes32(0), bytes32(inhibitor));
addFailedRequest(address(uint160(0)), makeExit(0), 1);
// Check that adding a request fails.
addFailedRequest(address(this), makeExit(2), 1);

vm.store(addr, bytes32(0), bytes32(inhibitor-1));
// Now re-enable the queue through a system call with no input.
vm.prank(sysaddr);
(ret, data) = addr.call("");
assertStorage(excess_slot, inhibitor-target_per_block-1, "didn't expect excess to be reset");
assertEq(ret, true);
assertEq(data.length, 0, "system call should return empty data since there are no requests");
assertStorage(excess_slot, 0, "expected zero excess requests after re-enabling queue");

// Check that adding a requests succeeds again.
addRequest(address(this), makeExit(3), 1);
}

// testQueueDisableFeeReset verifies that re-enabling the queue resets the fee to 1.
function testQueueDisableFeeReset() public {
uint256 requestCount = max_per_block*4;
for (uint64 i = 0; i < requestCount; i++) {
uint256 fee = getCurrentFee();
addRequest(address(this), makeExit(uint256(i)), fee);
}
assertStorage(count_slot, requestCount, "unexpected request count");

// Disable the queue with a system call that carries input data.
vm.prank(sysaddr);
(bool ret, bytes memory data) = addr.call(hex"01");
assertEq(ret, true);
assertEq(data.length, max_per_block*68, "system call should drain the queue");
assertStorage(excess_slot, inhibitor, "expected inhibitor in excess storage slot");

// Now re-enable the queue through a system call with no input.
vm.prank(sysaddr);
(ret, data) = addr.call("");
assertEq(ret, true);
assertEq(data.length, max_per_block*68, "system call should drain the queue");
assertStorage(excess_slot, 0, "expected zero excess requests after re-enabling queue");

// Check that adding a requests succeeds again with fee 1.
addRequest(address(this), makeExit(999), 1);
}

// --------------------------------------------------------------------------
Expand Down Expand Up @@ -249,4 +325,11 @@ contract BuilderExitTest is Test {
}
return pk;
}

// getCurrentFee returns the current fee computed by the system contract.
function getCurrentFee() internal view returns(uint256) {
(bool ok, bytes memory data) = addr.staticcall("");
assert(ok);
return uint256(bytes32(data));
}
}
Loading