Skip to content

Commit

Permalink
Forced exit re-use (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
k1rill-fedoseev authored Sep 16, 2023
1 parent a6cd424 commit 9b54085
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 9 deletions.
13 changes: 12 additions & 1 deletion src/zkbob/ZkBobPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex
event CommitForcedExit(
uint256 indexed nullifier, address operator, address to, uint256 amount, uint256 exitStart, uint256 exitEnd
);
event CancelForcedExit(uint256 indexed nullifier);
event ForcedExit(uint256 indexed index, uint256 indexed nullifier, address to, uint256 amount);

constructor(
Expand Down Expand Up @@ -369,6 +370,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex
uint256 root = roots[_index];
require(root > 0, "ZkBobPool: transfer index out of bounds");
require(nullifiers[_nullifier] == 0, "ZkBobPool: doublespend detected");
require(committedForcedExits[_nullifier] == 0, "ZkBobPool: already exists");

uint256[5] memory transfer_pub = [
root,
Expand Down Expand Up @@ -405,14 +407,16 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex
* @param _amount total account balance to withdraw.
* @param _exitStart exit window start timestamp, should match one calculated in commitForcedExit.
* @param _exitEnd exit window end timestamp, should match one calculated in commitForcedExit.
* @param _cancel cancel a previously submitted expired forced exit instead of executing it.
*/
function executeForcedExit(
uint256 _nullifier,
address _operator,
address _to,
uint256 _amount,
uint256 _exitStart,
uint256 _exitEnd
uint256 _exitEnd,
bool _cancel
)
external
{
Expand All @@ -421,6 +425,13 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex
committedForcedExits[_nullifier] == _hashForcedExit(_operator, _to, _amount, _exitStart, _exitEnd),
"ZkBobPool: invalid forced exit"
);
if (_cancel) {
require(block.timestamp >= _exitEnd, "ZkBobPool: exit not expired");
delete committedForcedExits[_nullifier];

emit CancelForcedExit(_nullifier);
return;
}

require(_operator == address(0) || _operator == msg.sender, "ZkBobPool: invalid caller");
require(block.timestamp >= _exitStart && block.timestamp < _exitEnd, "ZkBobPool: exit not allowed");
Expand Down
5 changes: 4 additions & 1 deletion test/interfaces/IZkBobPoolAdmin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ interface IZkBobPoolAdmin {

function transact() external;

function committedForcedExits(uint256 _nullifier) external view returns (bytes32);

function commitForcedExit(
address _operator,
address _to,
Expand All @@ -44,7 +46,8 @@ interface IZkBobPoolAdmin {
address _to,
uint256 _amount,
uint256 _exitStart,
uint256 _exitEnd
uint256 _exitEnd,
bool _cancel
)
external;

Expand Down
47 changes: 40 additions & 7 deletions test/zkbob/ZkBobPool.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -317,27 +317,27 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest {
assertEq(pool.nullifiers(nullifier), 0);

vm.expectRevert("ZkBobPool: invalid forced exit");
pool.executeForcedExit(nullifier ^ 1, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd);
pool.executeForcedExit(nullifier ^ 1, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd, false);

vm.expectRevert("ZkBobPool: invalid forced exit");
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, block.timestamp, exitEnd);
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, block.timestamp, exitEnd, false);

vm.expectRevert("ZkBobPool: invalid caller");
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd);
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd, false);

vm.startPrank(user2);
vm.expectRevert("ZkBobPool: exit not allowed");
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd);
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd, false);

skip(25 hours);
vm.expectRevert("ZkBobPool: exit not allowed");
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd);
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd, false);

rewind(23 hours);
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd);
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd, false);

vm.expectRevert("ZkBobPool: doublespend detected");
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd);
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd, false);

assertEq(IERC20(token).balanceOf(user2), 0.4 ether / D);
assertEq(pool.nullifiers(nullifier), 0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead0000000000000080);
Expand All @@ -348,6 +348,39 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest {
vm.stopPrank();
}

function testCancelForcedExit() public {
bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.01 ether / D);
_transact(data);

uint256 nullifier = _randFR();
pool.commitForcedExit(user2, user2, 0.4 ether / D / denominator, 128, nullifier, _randFR(), _randProof());
uint256 exitStart = block.timestamp + 1 hours;
uint256 exitEnd = block.timestamp + 24 hours;
bytes32 hash = pool.committedForcedExits(nullifier);
assertNotEq(hash, bytes32(0));

vm.expectRevert("ZkBobPool: exit not expired");
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd, true);
vm.expectRevert("ZkBobPool: already exists");
pool.commitForcedExit(user2, user2, 0.4 ether / D / denominator, 128, nullifier, _randFR(), _randProof());

skip(12 hours);

vm.expectRevert("ZkBobPool: exit not expired");
pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd, true);
vm.expectRevert("ZkBobPool: already exists");
pool.commitForcedExit(user2, user2, 0.4 ether / D / denominator, 128, nullifier, _randFR(), _randProof());

skip(24 hours);

pool.executeForcedExit(nullifier, user2, user2, 0.4 ether / D / denominator, exitStart, exitEnd, true);
assertEq(pool.committedForcedExits(nullifier), bytes32(0));

pool.commitForcedExit(user2, user2, 0.4 ether / D / denominator, 128, nullifier, _randFR(), _randProof());
assertNotEq(pool.committedForcedExits(nullifier), bytes32(0));
assertNotEq(pool.committedForcedExits(nullifier), hash);
}

function testRejectNegativeDeposits() public {
bytes memory data1 = _encodePermitDeposit(int256(0.99 ether / D), 0.01 ether / D);
_transact(data1);
Expand Down

0 comments on commit 9b54085

Please sign in to comment.