Skip to content

Commit

Permalink
Simplify storage mappings for forced exits
Browse files Browse the repository at this point in the history
  • Loading branch information
k1rill-fedoseev committed Sep 11, 2023
1 parent 942a648 commit ba1a838
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 37 deletions.
8 changes: 0 additions & 8 deletions src/interfaces/IZkBobPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,6 @@ pragma solidity ^0.8.0;
import "./IZkBobAccounting.sol";

interface IZkBobPool {
struct ForcedExitParams {
address to;
uint64 amount;
address operator;
uint40 exitStart;
uint40 exitEnd;
}

function pool_id() external view returns (uint256);

function denominator() external view returns (uint256);
Expand Down
85 changes: 64 additions & 21 deletions src/zkbob/ZkBobPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex
IZkBobDirectDepositQueue public immutable direct_deposit_queue;

uint256[2] private __deprecatedGap;
mapping(uint256 => ForcedExitParams) public committedForcedExits;
mapping(uint256 => bytes32) public committedForcedExits;
IEnergyRedeemer public redeemer;
IZkBobAccounting public accounting;
uint96 public pool_index;
Expand All @@ -68,7 +68,9 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex

event Message(uint256 indexed index, bytes32 indexed hash, bytes message);

event CommitForcedExit(uint256 indexed nullifier, uint40 exitStart, uint40 exitEnd);
event CommitForcedExit(
uint256 indexed nullifier, address operator, address to, uint256 amount, uint256 exitStart, uint256 exitEnd
);
event ForcedExit(uint256 indexed index, uint256 indexed nullifier, address to, uint256 amount);

constructor(
Expand Down Expand Up @@ -223,7 +225,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex

uint256 nullifier = _transfer_nullifier();
{
require(nullifiers[nullifier] < 2, "ZkBobPool: doublespend detected");
require(nullifiers[nullifier] == 0, "ZkBobPool: doublespend detected");
require(_transfer_index() <= poolIndex, "ZkBobPool: transfer index out of bounds");
require(transfer_verifier.verifyProof(_transfer_pub(), _transfer_proof()), "ZkBobPool: bad transfer proof");
require(tree_verifier.verifyProof(_tree_pub(roots[poolIndex]), _tree_proof()), "ZkBobPool: bad tree proof");
Expand Down Expand Up @@ -338,6 +340,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex

/**
* @dev Commits a forced withdrawal transaction for future execution after a set delay.
* Forced exits can be executed during 23 hours after 1 hour passed since its commitment.
* Account cannot be recovered after such forced exit.
* any remaining or newly sent funds would be lost forever.
* Accumulated account energy is forfeited.
Expand Down Expand Up @@ -365,7 +368,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] < 2, "ZkBobPool: doublespend detected");
require(nullifiers[_nullifier] == 0, "ZkBobPool: doublespend detected");

uint256[5] memory transfer_pub = [
root,
Expand All @@ -376,17 +379,17 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex
];
require(transfer_verifier.verifyProof(transfer_pub, _transfer_proof), "ZkBobPool: bad transfer proof");

nullifiers[_nullifier] = 1;
committedForcedExits[_nullifier] = ForcedExitParams({
to: _to,
amount: uint64(_amount),
operator: _operator,
exitStart: uint40(block.timestamp + FORCED_EXIT_MIN_DELAY),
exitEnd: uint40(block.timestamp + FORCED_EXIT_MAX_DELAY)
});
committedForcedExits[_nullifier] = _hashForcedExit(
_operator, _to, _amount, block.timestamp + FORCED_EXIT_MIN_DELAY, block.timestamp + FORCED_EXIT_MAX_DELAY
);

emit CommitForcedExit(
_nullifier, uint40(block.timestamp + FORCED_EXIT_MIN_DELAY), uint40(block.timestamp + FORCED_EXIT_MAX_DELAY)
_nullifier,
_operator,
_to,
_amount,
block.timestamp + FORCED_EXIT_MIN_DELAY,
block.timestamp + FORCED_EXIT_MAX_DELAY
);
}

Expand All @@ -397,23 +400,40 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex
* any remaining or newly sent funds would be lost forever.
* Accumulated account energy is forfeited.
* @param _nullifier transfer nullifier to be used for withdrawal.
* @param _operator operator address set during commitForcedExit.
* @param _to withdrawn funds receiver.
* @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.
*/
function executeForcedExit(uint256 _nullifier) external {
require(nullifiers[_nullifier] == 1, "ZkBobPool: forced exit not registered");
function executeForcedExit(
uint256 _nullifier,
address _operator,
address _to,
uint256 _amount,
uint256 _exitStart,
uint256 _exitEnd
)
external
{
require(nullifiers[_nullifier] == 0, "ZkBobPool: doublespend detected");
require(
committedForcedExits[_nullifier] == _hashForcedExit(_operator, _to, _amount, _exitStart, _exitEnd),
"ZkBobPool: invalid forced exit"
);

ForcedExitParams memory exit = committedForcedExits[_nullifier];
require(exit.operator == address(0) || exit.operator == msg.sender, "ZkBobPool: invalid caller");
require(block.timestamp >= exit.exitStart && block.timestamp < exit.exitEnd, "ZkBobPool: exit not allowed");
require(_operator == address(0) || _operator == msg.sender, "ZkBobPool: invalid caller");
require(block.timestamp >= _exitStart && block.timestamp < _exitEnd, "ZkBobPool: exit not allowed");

(IZkBobAccounting acc, uint96 poolIndex) = (accounting, pool_index);
if (address(acc) != address(0)) {
acc.recordOperation(IZkBobAccounting.TxType.ForcedExit, address(0), int256(uint256(exit.amount)));
acc.recordOperation(IZkBobAccounting.TxType.ForcedExit, address(0), int256(_amount));
}
nullifiers[_nullifier] = poolIndex | uint256(0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead0000000000000000);

IERC20(token).safeTransfer(exit.to, exit.amount * TOKEN_DENOMINATOR / TOKEN_NUMERATOR);
IERC20(token).safeTransfer(_to, _amount * TOKEN_DENOMINATOR / TOKEN_NUMERATOR);

emit ForcedExit(poolIndex, _nullifier, exit.to, exit.amount);
emit ForcedExit(poolIndex, _nullifier, _to, _amount);
}

/**
Expand Down Expand Up @@ -448,6 +468,29 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex
emit WithdrawFee(_operator, fee);
}

/**
* @dev Calculates forced exit operation hash.
* @param _operator operator address.
* @param _to withdrawn funds receiver.
* @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.
* @return operation hash.
*/
function _hashForcedExit(
address _operator,
address _to,
uint256 _amount,
uint256 _exitStart,
uint256 _exitEnd
)
internal
pure
returns (bytes32)
{
return keccak256(abi.encode(_operator, _to, _amount, _exitStart, _exitEnd));
}

/**
* @dev Tells if caller is the contract owner.
* Gives ownership rights to the proxy admin as well.
Expand Down
10 changes: 9 additions & 1 deletion test/interfaces/IZkBobPoolAdmin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@ interface IZkBobPoolAdmin {
)
external;

function executeForcedExit(uint256 _nullifier) external;
function executeForcedExit(
uint256 _nullifier,
address _operator,
address _to,
uint256 _amount,
uint256 _exitStart,
uint256 _exitEnd
)
external;

function appendDirectDeposits(
uint256 _root_after,
Expand Down
22 changes: 15 additions & 7 deletions test/zkbob/ZkBobPool.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -310,26 +310,34 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest {

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;

assertEq(IERC20(token).balanceOf(user2), 0);
assertEq(pool.nullifiers(nullifier), 1);
assertEq(pool.nullifiers(nullifier), 0);

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

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

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

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

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

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

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

assertEq(IERC20(token).balanceOf(user2), 0.4 ether / D);
assertEq(pool.nullifiers(nullifier), 0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead0000000000000080);
Expand Down

0 comments on commit ba1a838

Please sign in to comment.