From 95d8750a47e9f15e052707ce7510360dad279c01 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Fri, 24 Nov 2023 16:38:41 +0300 Subject: [PATCH 01/40] Move tree update in a separate function --- src/zkbob/ZkBobPool.sol | 53 +++++--- src/zkbob/utils/CustomABIDecoder.sol | 66 +++++----- src/zkbob/utils/Parameters.sol | 6 - src/zkbob/utils/PriorityQueue.sol | 81 ++++++++++++ test/interfaces/IZkBobPoolAdmin.sol | 8 +- test/zkbob/ZkBobPool.t.sol | 181 ++++++++++++++++++--------- 6 files changed, 281 insertions(+), 114 deletions(-) create mode 100644 src/zkbob/utils/PriorityQueue.sol diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index fb89938..75c173b 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -24,6 +24,7 @@ import "../utils/Ownable.sol"; import "../proxy/EIP1967Admin.sol"; import "../interfaces/IEnergyRedeemer.sol"; import "../utils/ExternalSload.sol"; +import {PriorityQueue, PriorityOperation} from "./utils/PriorityQueue.sol"; /** * @title ZkBobPool @@ -31,6 +32,7 @@ import "../utils/ExternalSload.sol"; */ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, ExternalSload { using SafeERC20 for IERC20; + using PriorityQueue for PriorityQueue.Queue; uint256 internal constant MAX_POOL_ID = 0xffffff; bytes4 internal constant MESSAGE_PREFIX_COMMON_V1 = 0x00000000; @@ -61,6 +63,8 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex mapping(address => uint256) public accumulatedFee; + PriorityQueue.Queue pendingCommitments; + event UpdateOperatorManager(address manager); event UpdateAccounting(address accounting); event UpdateRedeemer(address redeemer); @@ -147,6 +151,12 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex return TOKEN_NUMERATOR == 1 ? TOKEN_DENOMINATOR : (1 << 255) | TOKEN_NUMERATOR; } + function pendingCommitment() external view returns (uint256 commitment) { + PriorityOperation memory op = pendingCommitments.front(); + require(op.commitment != 0, "ZkBobPool: no pending commitment"); + return op.commitment; + } + /** * @dev Updates used accounting module. * Callable only by the contract owner / proxy admin. @@ -226,14 +236,15 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex uint256 nullifier = _transfer_nullifier(); { + require(msg.sender == _memo_prover_address(), "ZkBobPool: unauthorized"); 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"); + + _appendCommitment(_transfer_out_commit(), uint64(_memo_tree_update_fee())); nullifiers[nullifier] = uint256(keccak256(abi.encodePacked(_transfer_out_commit(), _transfer_delta()))); - poolIndex += 128; - roots[poolIndex] = _tree_root_after(); + bytes memory message = _memo_message(); // restrict memo message prefix (items count in little endian) to be < 2**16 require(bytes4(message) & 0x0000ffff == MESSAGE_PREFIX_COMMON_V1, "ZkBobPool: bad message prefix"); @@ -244,7 +255,10 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex emit Message(poolIndex, _all_messages_hash, message); } - uint256 fee = _memo_fee(); + uint256 transactFee = _memo_transact_fee(); + uint256 treeUpdateFee = _memo_tree_update_fee(); + uint256 fee = transactFee + treeUpdateFee; + int256 token_amount = transfer_token_delta + int256(fee); int256 energy_amount = _transfer_energy_amount(); @@ -286,25 +300,21 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex } if (fee > 0) { - accumulatedFee[msg.sender] += fee; + accumulatedFee[msg.sender] += transactFee; } } /** * @dev Appends a batch of direct deposits into a zkBob merkle tree. * Callable only by the current operator. - * @param _root_after new merkle tree root after append. * @param _indices list of indices for queued pending deposits. * @param _out_commit out commitment for output notes serialized from direct deposits. * @param _batch_deposit_proof snark proof for batch deposit verifier. - * @param _tree_proof snark proof for tree update verifier. */ function appendDirectDeposits( - uint256 _root_after, uint256[] calldata _indices, uint256 _out_commit, - uint256[8] memory _batch_deposit_proof, - uint256[8] memory _tree_proof + uint256[8] memory _batch_deposit_proof ) external onlyOperator @@ -322,11 +332,9 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex batch_deposit_verifier.verifyProof([hashsum], _batch_deposit_proof), "ZkBobPool: bad batch deposit proof" ); - uint256[3] memory tree_pub = [roots[poolIndex], _root_after, _out_commit]; - require(tree_verifier.verifyProof(tree_pub, _tree_proof), "ZkBobPool: bad tree proof"); + // TODO: what is about fees in this case? + _appendCommitment(_out_commit, uint64(0)); - poolIndex += 128; - roots[poolIndex] = _root_after; bytes32 message_hash = keccak256(message); bytes32 _all_messages_hash = keccak256(abi.encodePacked(all_messages_hash, message_hash)); all_messages_hash = _all_messages_hash; @@ -339,6 +347,16 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex emit Message(poolIndex, _all_messages_hash, message); } + function proveTreeUpdate(uint256 _commitment, uint256[8] calldata _proof, uint256 _rootAfter) external { + PriorityOperation memory pendindCommitment = pendingCommitments.popFront(); + require(pendindCommitment.commitment == _commitment, "ZkBobPool: commitment mismatch"); + uint256[3] memory tree_pub = [roots[pool_index], _rootAfter, _commitment]; + require(tree_verifier.verifyProof(tree_pub, _proof), "ZkBobPool: bad tree proof"); + pool_index += 128; + roots[pool_index] = _rootAfter; + accumulatedFee[msg.sender] += pendindCommitment.fee; + } + /** * @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. @@ -512,4 +530,11 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex function _isOwner() internal view override returns (bool) { return super._isOwner() || _admin() == _msgSender(); } + + function _appendCommitment(uint256 _commitment, uint64 _fee) internal { + pendingCommitments.pushBack(PriorityOperation({ + commitment: _commitment, + fee: _fee + })); + } } diff --git a/src/zkbob/utils/CustomABIDecoder.sol b/src/zkbob/utils/CustomABIDecoder.sol index 35a242d..8927503 100644 --- a/src/zkbob/utils/CustomABIDecoder.sol +++ b/src/zkbob/utils/CustomABIDecoder.sol @@ -55,24 +55,7 @@ contract CustomABIDecoder { } } - uint256 constant tree_root_after_pos = transfer_proof_pos + transfer_proof_size; - uint256 constant tree_root_after_size = 32; - - function _tree_root_after() internal pure returns (uint256 r) { - r = _loaduint256(tree_root_after_pos); - } - - uint256 constant tree_proof_pos = tree_root_after_pos + tree_root_after_size; - uint256 constant tree_proof_size = 256; - - function _tree_proof() internal pure returns (uint256[8] calldata r) { - uint256 pos = tree_proof_pos; - assembly { - r := pos - } - } - - uint256 constant tx_type_pos = tree_proof_pos + tree_proof_size; + uint256 constant tx_type_pos = transfer_proof_pos + transfer_proof_size; uint256 constant tx_type_size = 2; uint256 constant tx_type_mask = (1 << (tx_type_size * 8)) - 1; @@ -124,17 +107,17 @@ contract CustomABIDecoder { function _memo_fixed_size() internal pure returns (uint256 r) { uint256 t = _tx_type(); if (t == 0 || t == 1) { - // fee - // 8 - r = 8; - } else if (t == 2) { - // fee + native amount + recipient - // 8 + 8 + 20 + // prover address + transact fee + tree update fee + // 20 + 8 + 8 r = 36; + } else if (t == 2) { + // prover address + transact fee + tree update fee + native amount + recipient + // 20 + 8 + 8 + 8 + 20 + r = 64; } else if (t == 3) { - // fee + deadline + address - // 8 + 8 + 20 - r = 36; + // prover address + transact fee + tree update fee + deadline + address + // 20 + 8 + 8 + 8 + 20 + r = 64; } else { revert(); } @@ -150,17 +133,32 @@ contract CustomABIDecoder { } } - uint256 constant memo_fee_pos = memo_data_pos; - uint256 constant memo_fee_size = 8; - uint256 constant memo_fee_mask = (1 << (memo_fee_size * 8)) - 1; + uint256 constant memo_prover_address_pos = memo_data_pos; + uint256 constant memo_prover_address_size = 20; + + function _memo_prover_address() internal pure returns (address r) { + r = address(uint160(_loaduint256(memo_prover_address_pos + memo_prover_address_size - uint256_size))); + } + + uint256 constant memo_transact_fee_pos = memo_prover_address_pos + memo_prover_address_size; + uint256 constant memo_transact_fee_size = 8; + uint256 constant memo_transact_fee_mask = (1 << (memo_transact_fee_size * 8)) - 1; + + function _memo_transact_fee() internal pure returns (uint256 r) { + r = _loaduint256(memo_transact_fee_pos + memo_transact_fee_size - uint256_size) & memo_transact_fee_mask; + } + + uint256 constant memo_tree_update_fee_pos = memo_transact_fee_pos + memo_transact_fee_size; + uint256 constant memo_tree_update_fee_size = 8; + uint256 constant memo_tree_update_fee_mask = (1 << (memo_tree_update_fee_size * 8)) - 1; - function _memo_fee() internal pure returns (uint256 r) { - r = _loaduint256(memo_fee_pos + memo_fee_size - uint256_size) & memo_fee_mask; + function _memo_tree_update_fee() internal pure returns (uint256 r) { + r = _loaduint256(memo_tree_update_fee_pos + memo_tree_update_fee_size - uint256_size) & memo_tree_update_fee_mask; } // Withdraw specific data - uint256 constant memo_native_amount_pos = memo_fee_pos + memo_fee_size; + uint256 constant memo_native_amount_pos = memo_tree_update_fee_pos + memo_tree_update_fee_size; uint256 constant memo_native_amount_size = 8; uint256 constant memo_native_amount_mask = (1 << (memo_native_amount_size * 8)) - 1; @@ -177,7 +175,7 @@ contract CustomABIDecoder { // Permittable token deposit specific data - uint256 constant memo_permit_deadline_pos = memo_fee_pos + memo_fee_size; + uint256 constant memo_permit_deadline_pos = memo_tree_update_fee_pos + memo_tree_update_fee_size; uint256 constant memo_permit_deadline_size = 8; function _memo_permit_deadline() internal pure returns (uint64 r) { diff --git a/src/zkbob/utils/Parameters.sol b/src/zkbob/utils/Parameters.sol index a27f2fa..da56895 100644 --- a/src/zkbob/utils/Parameters.sol +++ b/src/zkbob/utils/Parameters.sol @@ -20,12 +20,6 @@ abstract contract Parameters is CustomABIDecoder { r[4] = uint256(keccak256(_memo_data())) % R; } - function _tree_pub(uint256 _root_before) internal view returns (uint256[3] memory r) { - r[0] = _root_before; - r[1] = _tree_root_after(); - r[2] = _transfer_out_commit(); - } - // NOTE only valid in the context of normal deposit (tx_type=0) function _deposit_spender() internal pure returns (address) { (bytes32 r, bytes32 vs) = _sign_r_vs(); diff --git a/src/zkbob/utils/PriorityQueue.sol b/src/zkbob/utils/PriorityQueue.sol new file mode 100644 index 0000000..4c41522 --- /dev/null +++ b/src/zkbob/utils/PriorityQueue.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.13; + +/// @notice The structure that contains meta information of the L2 transaction that was requested from L1 +/// @dev The weird size of fields was selected specifically to minimize the structure storage size +/// @param canonicalTxHash Hashed L2 transaction data that is needed to process it +/// @param expirationTimestamp Expiration timestamp for this request (must be satisfied before) +/// @param layer2Tip Additional payment to the validator as an incentive to perform the operation +struct PriorityOperation { + uint256 commitment; + uint64 fee; +} + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev The library provides the API to interact with the priority queue container +/// @dev Order of processing operations from queue - FIFO (Fist in - first out) +library PriorityQueue { + using PriorityQueue for Queue; + + /// @notice Container that stores priority operations + /// @param data The inner mapping that saves priority operation by its index + /// @param head The pointer to the first unprocessed priority operation, equal to the tail if the queue is empty + /// @param tail The pointer to the free slot + struct Queue { + mapping(uint256 => PriorityOperation) data; + uint256 tail; + uint256 head; + } + + /// @notice Returns zero if and only if no operations were processed from the queue + /// @return Index of the oldest priority operation that wasn't processed yet + function getFirstUnprocessedPriorityTx(Queue storage _queue) internal view returns (uint256) { + return _queue.head; + } + + /// @return The total number of priority operations that were added to the priority queue, including all processed ones + function getTotalPriorityTxs(Queue storage _queue) internal view returns (uint256) { + return _queue.tail; + } + + /// @return The total number of unprocessed priority operations in a priority queue + function getSize(Queue storage _queue) internal view returns (uint256) { + return uint256(_queue.tail - _queue.head); + } + + /// @return Whether the priority queue contains no operations + function isEmpty(Queue storage _queue) internal view returns (bool) { + return _queue.tail == _queue.head; + } + + /// @notice Add the priority operation to the end of the priority queue + function pushBack(Queue storage _queue, PriorityOperation memory _operation) internal { + // Save value into the stack to avoid double reading from the storage + uint256 tail = _queue.tail; + + _queue.data[tail] = _operation; + _queue.tail = tail + 1; + } + + /// @return The first unprocessed priority operation from the queue + function front(Queue storage _queue) internal view returns (PriorityOperation memory) { + require(!_queue.isEmpty(), "D"); // priority queue is empty + + return _queue.data[_queue.head]; + } + + /// @notice Remove the first unprocessed priority operation from the queue + /// @return priorityOperation that was popped from the priority queue + function popFront(Queue storage _queue) internal returns (PriorityOperation memory priorityOperation) { + require(!_queue.isEmpty(), "s"); // priority queue is empty + + // Save value into the stack to avoid double reading from the storage + uint256 head = _queue.head; + + priorityOperation = _queue.data[head]; + delete _queue.data[head]; + _queue.head = head + 1; + } +} diff --git a/test/interfaces/IZkBobPoolAdmin.sol b/test/interfaces/IZkBobPoolAdmin.sol index b215451..29b9081 100644 --- a/test/interfaces/IZkBobPoolAdmin.sol +++ b/test/interfaces/IZkBobPoolAdmin.sol @@ -11,6 +11,8 @@ interface IZkBobPoolAdmin { function pool_index() external view returns (uint256); + function pendingCommitment() external view returns (uint256); + function initialize(uint256 _root) external; function setTokenSeller(address _tokenSeller) external; @@ -27,6 +29,8 @@ interface IZkBobPoolAdmin { function transact() external; + function proveTreeUpdate(uint256, uint256[8] memory, uint256) external; + function committedForcedExits(uint256 _nullifier) external view returns (bytes32); function commitForcedExit( @@ -52,11 +56,9 @@ interface IZkBobPoolAdmin { external; function appendDirectDeposits( - uint256 _root_after, uint256[] calldata _indices, uint256 _out_commit, - uint256[8] memory _batch_deposit_proof, - uint256[8] memory _tree_proof + uint256[8] memory _batch_deposit_proof ) external; diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 8428f9b..d45e3e2 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -155,11 +155,13 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { } function testSimpleTransaction() public { - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.01 ether / D); + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data1); + _proveTreeUpdate(); - bytes memory data2 = _encodeTransfer(0.01 ether / D); + bytes memory data2 = _encodeTransfer(0.005 ether / D, 0.005 ether / D, user2); _transact(data2); + _proveTreeUpdate(); vm.prank(user3); pool.withdrawFee(user2, user3); @@ -170,13 +172,15 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { assertEq(pool.pool_index(), 0); assertEq(pool.denominator(), denominator); - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.01 ether / D); + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data1); + _proveTreeUpdate(); assertEq(pool.pool_index(), 128); - bytes memory data2 = _encodeTransfer(0.01 ether / D); + bytes memory data2 = _encodeTransfer(0.005 ether / D, 0.005 ether / D, user2); _transact(data2); + _proveTreeUpdate(); assertEq(pool.pool_index(), 256); } @@ -226,11 +230,13 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { function testResetDailyLimits() public { deal(token, user1, 10 ether / D); - bytes memory data1 = _encodePermitDeposit(int256(5 ether / D), 0.01 ether / D); + bytes memory data1 = _encodePermitDeposit(int256(5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data1); + _proveTreeUpdate(); - bytes memory data2 = _encodeWithdrawal(user1, 4 ether / D, 0, 0); + bytes memory data2 = _encodeWithdrawal(user1, 4 ether / D, 0, 0, user2); _transact(data2); + _proveTreeUpdate(); assertEq(accounting.getLimitsFor(user1).dailyDepositCapUsage, 5 ether / D / denominator); assertEq(accounting.getLimitsFor(user1).dailyWithdrawalCapUsage, 4.01 ether / D / denominator); @@ -251,8 +257,9 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { } function testPermitDeposit() public { - bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.01 ether / D); + bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data); + _proveTreeUpdate(); vm.prank(user3); pool.withdrawFee(user2, user3); @@ -264,12 +271,14 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { function testMultiplePermitDeposits() public { for (uint256 i = 1; i < 10; i++) { deal(address(token), user1, 0.101 ether / D * i); - bytes memory data = _encodePermitDeposit(int256(0.1 ether / D) * int256(i), 0.001 ether / D * i); + bytes memory data = _encodePermitDeposit(int256(0.1 ether / D) * int256(i), 0.0005 ether / D * i, 0.0005 ether / D * i, user2); _transact(data); + _proveTreeUpdate(); } - bytes memory data2 = _encodeTransfer(0.01 ether / D); + bytes memory data2 = _encodeTransfer(0.005 ether / D, 0.005 ether / D, user2); _transact(data2); + _proveTreeUpdate(); vm.prank(user3); pool.withdrawFee(user2, user3); @@ -280,8 +289,9 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { vm.prank(user1); IERC20(token).approve(address(pool), 0.51 ether / D); - bytes memory data = _encodeDeposit(int256(0.5 ether / D), 0.01 ether / D); + bytes memory data = _encodeDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data); + _proveTreeUpdate(); vm.prank(user3); pool.withdrawFee(user2, user3); @@ -291,11 +301,13 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { } function testWithdrawal() public { - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.01 ether / D); + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data1); + _proveTreeUpdate(); - bytes memory data2 = _encodeWithdrawal(user1, 0.1 ether / D, 0, 0); + bytes memory data2 = _encodeWithdrawal(user1, 0.1 ether / D, 0, 0, user2); _transact(data2); + _proveTreeUpdate(); vm.prank(user3); pool.withdrawFee(user2, user3); @@ -305,8 +317,9 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { } function testForcedExit() public { - bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.01 ether / D); + bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data); + _proveTreeUpdate(); uint256 nullifier = _randFR(); pool.commitForcedExit(user2, user2, 0.4 ether / D / denominator, 128, nullifier, _randFR(), _randProof()); @@ -349,8 +362,9 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { } function testCancelForcedExit() public { - bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.01 ether / D); + bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data); + _proveTreeUpdate(); uint256 nullifier = _randFR(); pool.commitForcedExit(user2, user2, 0.4 ether / D / denominator, 128, nullifier, _randFR(), _randProof()); @@ -382,16 +396,17 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { } function testRejectNegativeDeposits() public { - bytes memory data1 = _encodePermitDeposit(int256(0.99 ether / D), 0.01 ether / D); + bytes memory data1 = _encodePermitDeposit(int256(0.99 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data1); + _proveTreeUpdate(); - bytes memory data2 = _encodePermitDeposit(-int256(0.5 ether / D), 1 ether / D); + bytes memory data2 = _encodePermitDeposit(-int256(0.5 ether / D), 0.5 ether / D, 0.5 ether / D, user2); _transactReverted(data2, "ZkBobPool: incorrect deposit amounts"); vm.prank(user1); IERC20(token).approve(address(pool), 0.5 ether / D); - bytes memory data3 = _encodeDeposit(-int256(0.5 ether / D), 1 ether / D); + bytes memory data3 = _encodeDeposit(-int256(0.5 ether / D), 0.5 ether / D, 0.5 ether / D, user2); _transactReverted(data3, "ZkBobPool: incorrect deposit amounts"); } @@ -517,7 +532,7 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { vm.expectEmit(true, false, false, true); emit Message(128, bytes32(0), message); vm.prank(user2); - pool.appendDirectDeposits(_randFR(), indices, outCommitment, _randProof(), _randProof()); + pool.appendDirectDeposits(indices, outCommitment, _randProof()); } function testRefundDirectDeposit() public { @@ -580,16 +595,18 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { deal(address(token), address(user1), 10 ether / D); - bytes memory data = _encodePermitDeposit(int256(4 ether / D), 0.01 ether / D); + bytes memory data = _encodePermitDeposit(int256(4 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data); + _proveTreeUpdate(); - bytes memory data2 = _encodeWithdrawal(user1, 1 ether / D, 0, 0); + bytes memory data2 = _encodeWithdrawal(user1, 1 ether / D, 0, 0, user2); _transact(data2); + _proveTreeUpdate(); - bytes memory data3 = _encodePermitDeposit(int256(3 ether / D), 0.01 ether / D); + bytes memory data3 = _encodePermitDeposit(int256(3 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transactReverted(data3, "ZkBobAccounting: daily user deposit cap exceeded"); - bytes memory data4 = _encodeWithdrawal(user1, 2 ether / D, 0, 0); + bytes memory data4 = _encodeWithdrawal(user1, 2 ether / D, 0, 0, user2); _transactReverted(data4, "ZkBobAccounting: daily withdrawal cap exceeded"); assertEq(accounting.getLimitsFor(user1).dailyUserDepositCapUsage, 4 ether / D / denominator); @@ -618,16 +635,18 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { vm.deal(user1, 0); - bytes memory data1 = _encodePermitDeposit(int256(0.99 ether / D), 0.01 ether / D); + bytes memory data1 = _encodePermitDeposit(int256(0.99 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data1); + _proveTreeUpdate(); // user1 withdraws 0.4 BOB, 0.3 BOB gets converted to ETH uint256 quote2 = _quoteNativeSwap(0.3 ether / D); - bytes memory data2 = _encodeWithdrawal(user1, 0.4 ether / D, 0.3 ether / D, 0); + bytes memory data2 = _encodeWithdrawal(user1, 0.4 ether / D, 0.3 ether / D, 0, user2); _transact(data2); + _proveTreeUpdate(); // user1 withdraws 0.2 BOB, trying to convert 0.3 BOB to ETH - bytes memory data4 = _encodeWithdrawal(user1, 0.2 ether / D, 0.3 ether / D, 0); + bytes memory data4 = _encodeWithdrawal(user1, 0.2 ether / D, 0.3 ether / D, 0, user2); vm.prank(user2); (bool status, bytes memory returnData) = address(pool).call(data4); assert(!status); @@ -635,8 +654,9 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { address dummy = address(new DummyImpl(0)); uint256 quote3 = _quoteNativeSwap(0.3 ether / D); - bytes memory data3 = _encodeWithdrawal(dummy, 0.4 ether / D, 0.3 ether / D, 0); + bytes memory data3 = _encodeWithdrawal(dummy, 0.4 ether / D, 0.3 ether / D, 0, user2); _transact(data3); + _proveTreeUpdate(); vm.prank(user3); pool.withdrawFee(user2, user3); @@ -670,8 +690,9 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { 0, 0 ); - bytes memory data1 = _encodePermitDeposit(int256(250_000 ether / D), 0.01 ether / D); + bytes memory data1 = _encodePermitDeposit(int256(250_000 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data1); + _proveTreeUpdate(); accounting.setLimits( 0, 1_000_000 ether / D / denominator, @@ -682,11 +703,12 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { 0, 0 ); - bytes memory data2 = _encodeTransfer(200_000 ether / D); + bytes memory data2 = _encodeTransfer(100_000 ether / D, 100_000 ether / D, user2); _transactReverted(data2, "ZkBobAccounting: daily withdrawal cap exceeded"); - bytes memory data3 = _encodeTransfer(20_000 ether / D); + bytes memory data3 = _encodeTransfer(10_000 ether / D, 10_000 ether / D, user2); _transact(data3); + _proveTreeUpdate(); vm.prank(user3); pool.withdrawFee(user2, user3); @@ -698,8 +720,9 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { deal(token, user1, 2_000_000 ether / D); for (uint256 i = 0; i < 100; i++) { - bytes memory data1 = _encodePermitDeposit(int256(2_500 ether / D), 0.01 ether / D); + bytes memory data1 = _encodePermitDeposit(int256(2_500 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data1); + _proveTreeUpdate(); skip(6 hours + 1); } @@ -710,8 +733,9 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { pool.setEnergyRedeemer(redeemer); // 1e18 energy ~= account balance of 100k BOB across 10k tx indices - bytes memory data2 = _encodeWithdrawal(user1, 0, 0, 1e18); + bytes memory data2 = _encodeWithdrawal(user1, 0, 0, 1e18, user2); _transact(data2); + _proveTreeUpdate(); // max weekly tvl ~= 200k // max weekly tx count ~= 28 @@ -719,7 +743,7 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { assertApproxEqAbs(rewardToken.balanceOf(user1), 1785 ether, 200 ether); } - function _encodeDeposit(int256 _amount, uint256 _fee) internal returns (bytes memory) { + function _encodeDeposit(int256 _amount, uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal returns (bytes memory) { bytes32 nullifier = bytes32(_randFR()); (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk1, ECDSA.toEthSignedMessageHash(nullifier)); bytes memory data = abi.encodePacked( @@ -730,10 +754,19 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { uint112(0), int64(_amount / int256(denominator)) ); - for (uint256 i = 0; i < 17; i++) { + for (uint256 i = 0; i < 8; i++) { data = abi.encodePacked(data, _randFR()); } - data = abi.encodePacked(data, uint16(0), uint16(44), uint64(_fee / denominator), bytes4(0x01000000), _randFR()); + data = abi.encodePacked( + data, + uint16(0), + uint16(72), + prover, + uint64(_transactFee / denominator), + uint64(_treeUpdateFee / denominator), + bytes4(0x01000000), + _randFR() + ); return abi.encodePacked(data, r, uint256(s) + (v == 28 ? (1 << 255) : 0)); } @@ -741,7 +774,8 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { address _to, uint256 _amount, uint256 _nativeAmount, - uint256 _energyAmount + uint256 _energyAmount, + address prover ) internal returns (bytes memory) @@ -752,16 +786,23 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { _randFR(), uint48(0), -int112(int256(_energyAmount)), - int64(-int256((_amount / denominator) + 0.01 ether / D / denominator)) + int64(-int256((_amount / denominator) + 0.005 ether / D / denominator + 0.005 ether / D / denominator)) ); - for (uint256 i = 0; i < 17; i++) { + for (uint256 i = 0; i < 8; i++) { data = abi.encodePacked(data, _randFR()); } - return abi.encodePacked( + + data = abi.encodePacked( data, uint16(2), - uint16(72), - uint64(0.01 ether / D / denominator), + uint16(100) + ); + + return abi.encodePacked( + data, + prover, + uint64(0.005 ether / D / denominator), + uint64(0.005 ether / D / denominator), uint64(_nativeAmount / denominator), _to, bytes4(0x01000000), @@ -769,14 +810,23 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { ); } - function _encodeTransfer(uint256 _fee) internal returns (bytes memory) { + function _encodeTransfer(uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal returns (bytes memory) { bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, _randFR(), _randFR(), uint48(0), uint112(0), -int64(uint64(_fee / denominator)) + ZkBobPool.transact.selector, _randFR(), _randFR(), uint48(0), uint112(0), -int64(uint64((_transactFee + _treeUpdateFee) / denominator)) ); - for (uint256 i = 0; i < 17; i++) { + for (uint256 i = 0; i < 8; i++) { data = abi.encodePacked(data, _randFR()); } - return abi.encodePacked(data, uint16(1), uint16(44), uint64(_fee / denominator), bytes4(0x01000000), _randFR()); + return abi.encodePacked( + data, + uint16(1), + uint16(72), + prover, + uint64(_transactFee / denominator), + uint64(_treeUpdateFee / denominator), + bytes4(0x01000000), + _randFR() + ); } function _transact(bytes memory _data) internal { @@ -785,6 +835,12 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { require(status, "transact() reverted"); } + function _proveTreeUpdate() internal { + vm.startPrank(user2); + pool.proveTreeUpdate(pool.pendingCommitment(), _randProof(), _randFR()); + vm.stopPrank(); + } + function _transactReverted(bytes memory _data, bytes memory _revertReason) internal { vm.prank(user2); (bool status, bytes memory returnData) = address(pool).call(_data); @@ -801,7 +857,7 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { return [_randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR()]; } - function _encodePermitDeposit(int256 _amount, uint256 _fee) internal returns (bytes memory) { + function _encodePermitDeposit(int256 _amount, uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal returns (bytes memory) { if (permitType == PermitType.Permit2) { vm.prank(user1); IERC20(token).approve(permit2, type(uint256).max); @@ -810,15 +866,19 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { uint256 expiry = block.timestamp + 1 hours; bytes32 nullifier = bytes32(_randFR()); - bytes32 digest; - if (permitType == PermitType.BOBPermit) { - digest = _digestSaltedPermit(user1, address(pool), uint256(_amount + int256(_fee)), expiry, nullifier); - } else if (permitType == PermitType.Permit2) { - digest = _digestPermit2(user1, address(pool), uint256(_amount + int256(_fee)), expiry, nullifier); - } else if (permitType == PermitType.USDCPermit) { - digest = _digestUSDCPermit(user1, address(pool), uint256(_amount + int256(_fee)), expiry, nullifier); + bytes memory signature; + { + bytes32 digest; + if (permitType == PermitType.BOBPermit) { + digest = _digestSaltedPermit(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); + } else if (permitType == PermitType.Permit2) { + digest = _digestPermit2(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); + } else if (permitType == PermitType.USDCPermit) { + digest = _digestUSDCPermit(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); + } + (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk1, digest); + signature = abi.encodePacked(r, uint256(s) + (v == 28 ? (1 << 255) : 0)); } - (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk1, digest); bytes memory data = abi.encodePacked( ZkBobPool.transact.selector, @@ -828,20 +888,27 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { uint112(0), int64(_amount / int256(denominator)) ); - for (uint256 i = 0; i < 17; i++) { + for (uint256 i = 0; i < 8; i++) { data = abi.encodePacked(data, _randFR()); } + data = abi.encodePacked( data, uint16(3), - uint16(72), - uint64(_fee / denominator), + uint16(100) + ); + + data = abi.encodePacked( + data, + prover, + uint64(_transactFee / denominator), + uint64(_treeUpdateFee / denominator), uint64(expiry), user1, bytes4(0x01000000), _randFR() ); - return abi.encodePacked(data, r, uint256(s) + (v == 28 ? (1 << 255) : 0)); + return abi.encodePacked(data, signature); } function _digestSaltedPermit( From ffa96ec644ade4f5920a1011f3ce4a7cce446f9d Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Fri, 24 Nov 2023 17:51:16 +0300 Subject: [PATCH 02/40] Add grace period --- src/zkbob/ZkBobPool.sol | 29 ++++++++++++++++++++++++----- src/zkbob/utils/PriorityQueue.sol | 2 ++ test/zkbob/ZkBobPool.t.sol | 2 +- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 75c173b..2d9f686 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -38,6 +38,8 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex bytes4 internal constant MESSAGE_PREFIX_COMMON_V1 = 0x00000000; uint256 internal constant FORCED_EXIT_MIN_DELAY = 1 hours; uint256 internal constant FORCED_EXIT_MAX_DELAY = 24 hours; + // TODO: make configurable + uint64 internal constant TREE_UPDATE_GRACE_PERIOD = 5 minutes; uint256 internal immutable TOKEN_DENOMINATOR; uint256 internal constant TOKEN_NUMERATOR = 1; @@ -63,7 +65,8 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex mapping(address => uint256) public accumulatedFee; - PriorityQueue.Queue pendingCommitments; + PriorityQueue.Queue internal pendingCommitments; + uint64 internal lastTreeUpdateTimestamp; event UpdateOperatorManager(address manager); event UpdateAccounting(address accounting); @@ -71,6 +74,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex event WithdrawFee(address indexed operator, uint256 fee); event Message(uint256 indexed index, bytes32 indexed hash, bytes message); + event RootUpdated(uint256 indexed index, uint256 root); event CommitForcedExit( uint256 indexed nullifier, address operator, address to, uint256 amount, uint256 exitStart, uint256 exitEnd @@ -241,7 +245,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex require(_transfer_index() <= poolIndex, "ZkBobPool: transfer index out of bounds"); require(transfer_verifier.verifyProof(_transfer_pub(), _transfer_proof()), "ZkBobPool: bad transfer proof"); - _appendCommitment(_transfer_out_commit(), uint64(_memo_tree_update_fee())); + _appendCommitment(_transfer_out_commit(), uint64(_memo_tree_update_fee()), msg.sender); nullifiers[nullifier] = uint256(keccak256(abi.encodePacked(_transfer_out_commit(), _transfer_delta()))); @@ -252,6 +256,8 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex bytes32 _all_messages_hash = keccak256(abi.encodePacked(all_messages_hash, message_hash)); all_messages_hash = _all_messages_hash; pool_index = poolIndex; + + // TODO: is it fine that index wasn't updated? emit Message(poolIndex, _all_messages_hash, message); } @@ -333,7 +339,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex ); // TODO: what is about fees in this case? - _appendCommitment(_out_commit, uint64(0)); + _appendCommitment(_out_commit, uint64(0), msg.sender); bytes32 message_hash = keccak256(message); bytes32 _all_messages_hash = keccak256(abi.encodePacked(all_messages_hash, message_hash)); @@ -344,17 +350,28 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex accumulatedFee[msg.sender] += totalFee; } + // TODO: is it fine that index wasn't updated? emit Message(poolIndex, _all_messages_hash, message); } function proveTreeUpdate(uint256 _commitment, uint256[8] calldata _proof, uint256 _rootAfter) external { PriorityOperation memory pendindCommitment = pendingCommitments.popFront(); require(pendindCommitment.commitment == _commitment, "ZkBobPool: commitment mismatch"); + + uint64 timestamp = pendindCommitment.timestamp; + if (timestamp < lastTreeUpdateTimestamp) { + timestamp = lastTreeUpdateTimestamp; + } + require(block.timestamp > timestamp + TREE_UPDATE_GRACE_PERIOD || pendindCommitment.prover == msg.sender, "ZkBobPool: unauthorized"); + uint256[3] memory tree_pub = [roots[pool_index], _rootAfter, _commitment]; require(tree_verifier.verifyProof(tree_pub, _proof), "ZkBobPool: bad tree proof"); pool_index += 128; roots[pool_index] = _rootAfter; accumulatedFee[msg.sender] += pendindCommitment.fee; + lastTreeUpdateTimestamp = uint64(block.timestamp); + + emit RootUpdated(pool_index, _rootAfter); } /** @@ -531,10 +548,12 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex return super._isOwner() || _admin() == _msgSender(); } - function _appendCommitment(uint256 _commitment, uint64 _fee) internal { + function _appendCommitment(uint256 _commitment, uint64 _fee, address _prover) internal { pendingCommitments.pushBack(PriorityOperation({ commitment: _commitment, - fee: _fee + fee: _fee, + prover: _prover, + timestamp: uint64(block.timestamp) })); } } diff --git a/src/zkbob/utils/PriorityQueue.sol b/src/zkbob/utils/PriorityQueue.sol index 4c41522..8d26c1e 100644 --- a/src/zkbob/utils/PriorityQueue.sol +++ b/src/zkbob/utils/PriorityQueue.sol @@ -9,7 +9,9 @@ pragma solidity ^0.8.13; /// @param layer2Tip Additional payment to the validator as an incentive to perform the operation struct PriorityOperation { uint256 commitment; + address prover; uint64 fee; + uint64 timestamp; } /// @author Matter Labs diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index d45e3e2..6655a25 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -530,7 +530,7 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { uint64(4.9 ether / D / denominator) // second deposit amount ); vm.expectEmit(true, false, false, true); - emit Message(128, bytes32(0), message); + emit Message(0, bytes32(0), message); vm.prank(user2); pool.appendDirectDeposits(indices, outCommitment, _randProof()); } From 406c2bfebe81632cfff6b8e10f54b1ab472aecce Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Mon, 27 Nov 2023 17:17:57 +0300 Subject: [PATCH 03/40] Refactor code, add tests --- src/zkbob/ZkBobPool.sol | 74 ++- .../manager/AllowListOperatorManager.sol | 99 +++ test/interfaces/IZkBobPoolAdmin.sol | 4 + test/zkbob/ZkBobPool.t.sol | 578 +++++++++--------- test/zkbob/ZkBobPoolDecentralized.t.sol | 227 +++++++ 5 files changed, 682 insertions(+), 300 deletions(-) create mode 100644 src/zkbob/manager/AllowListOperatorManager.sol create mode 100644 test/zkbob/ZkBobPoolDecentralized.t.sol diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 2d9f686..1811b1b 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -38,8 +38,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex bytes4 internal constant MESSAGE_PREFIX_COMMON_V1 = 0x00000000; uint256 internal constant FORCED_EXIT_MIN_DELAY = 1 hours; uint256 internal constant FORCED_EXIT_MAX_DELAY = 24 hours; - // TODO: make configurable - uint64 internal constant TREE_UPDATE_GRACE_PERIOD = 5 minutes; uint256 internal immutable TOKEN_DENOMINATOR; uint256 internal constant TOKEN_NUMERATOR = 1; @@ -67,10 +65,15 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex PriorityQueue.Queue internal pendingCommitments; uint64 internal lastTreeUpdateTimestamp; + + uint64 public gracePeriod = 5 minutes; + uint64 public minTreeUpdateFee = 0; event UpdateOperatorManager(address manager); event UpdateAccounting(address accounting); event UpdateRedeemer(address redeemer); + event UpdateGracePeriod(uint64 gracePeriod); + event UpdateMinTreeUpdateFee(uint64 minTreeUpdateFee); event WithdrawFee(address indexed operator, uint256 fee); event Message(uint256 indexed index, bytes32 indexed hash, bytes message); @@ -106,6 +109,10 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex direct_deposit_queue = IZkBobDirectDepositQueue(_direct_deposit_queue); TOKEN_DENOMINATOR = _denominator; + + // TODO: Hardcoded values for now + gracePeriod = 5 minutes; + minTreeUpdateFee = 0; } /** @@ -185,6 +192,16 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex emit UpdateRedeemer(address(_redeemer)); } + function setGracePeriod(uint64 _gracePeriod) external onlyOwner { + gracePeriod = _gracePeriod; + emit UpdateGracePeriod(_gracePeriod); + } + + function setMinTreeUpdateFee(uint64 _minTreeUpdateFee) external onlyOwner { + minTreeUpdateFee = _minTreeUpdateFee; + emit UpdateMinTreeUpdateFee(_minTreeUpdateFee); + } + function _root() internal view override returns (uint256) { return roots[_transfer_index()]; } @@ -263,6 +280,9 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex uint256 transactFee = _memo_transact_fee(); uint256 treeUpdateFee = _memo_tree_update_fee(); + + require(treeUpdateFee >= minTreeUpdateFee, "ZkBobPool: tree update fee is too low"); + uint256 fee = transactFee + treeUpdateFee; int256 token_amount = transfer_token_delta + int256(fee); @@ -338,8 +358,10 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex batch_deposit_verifier.verifyProof([hashsum], _batch_deposit_proof), "ZkBobPool: bad batch deposit proof" ); - // TODO: what is about fees in this case? - _appendCommitment(_out_commit, uint64(0), msg.sender); + // TODO: is it ok? + require(totalFee > minTreeUpdateFee, "ZkBobPool: tree update fee is too low"); + uint64 ddFee = uint64(totalFee) - minTreeUpdateFee; + _appendCommitment(_out_commit, minTreeUpdateFee, msg.sender); bytes32 message_hash = keccak256(message); bytes32 _all_messages_hash = keccak256(abi.encodePacked(all_messages_hash, message_hash)); @@ -347,28 +369,37 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex pool_index = poolIndex; if (totalFee > 0) { - accumulatedFee[msg.sender] += totalFee; + accumulatedFee[msg.sender] += ddFee; } // TODO: is it fine that index wasn't updated? emit Message(poolIndex, _all_messages_hash, message); } - function proveTreeUpdate(uint256 _commitment, uint256[8] calldata _proof, uint256 _rootAfter) external { - PriorityOperation memory pendindCommitment = pendingCommitments.popFront(); - require(pendindCommitment.commitment == _commitment, "ZkBobPool: commitment mismatch"); - - uint64 timestamp = pendindCommitment.timestamp; - if (timestamp < lastTreeUpdateTimestamp) { - timestamp = lastTreeUpdateTimestamp; - } - require(block.timestamp > timestamp + TREE_UPDATE_GRACE_PERIOD || pendindCommitment.prover == msg.sender, "ZkBobPool: unauthorized"); + /** + * @dev Updates pool index and merkle tree root if the provided proof is valid and + * the proof corresponds to the pending commitment. + * A prover that submitted the transfer proof has the grace period to submit the tree update proof. + * @param _commitment pending commitment to be proven. + * @param _proof snark proof for tree update verifier. + * @param _rootAfter new merkle tree root. + */ + function proveTreeUpdate( + uint256 _commitment, + uint256[8] calldata _proof, + uint256 _rootAfter + ) external onlyOperator { + PriorityOperation memory commitment = pendingCommitments.popFront(); + require(commitment.commitment == _commitment, "ZkBobPool: commitment mismatch"); + + _validateGracePeriod(commitment.timestamp, commitment.prover); uint256[3] memory tree_pub = [roots[pool_index], _rootAfter, _commitment]; require(tree_verifier.verifyProof(tree_pub, _proof), "ZkBobPool: bad tree proof"); + pool_index += 128; roots[pool_index] = _rootAfter; - accumulatedFee[msg.sender] += pendindCommitment.fee; + accumulatedFee[msg.sender] += commitment.fee; lastTreeUpdateTimestamp = uint64(block.timestamp); emit RootUpdated(pool_index, _rootAfter); @@ -556,4 +587,17 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex timestamp: uint64(block.timestamp) })); } + + function _validateGracePeriod(uint64 commitmentTimestamp, address privilegedProver) internal view { + // We calculate the beggining of the grace period either from the timestamp of the last tree update, + // or from the timestamp of the commitment, whichever is greater. + uint64 timestamp = commitmentTimestamp; + if (timestamp < lastTreeUpdateTimestamp) { + timestamp = lastTreeUpdateTimestamp; + } + require( + block.timestamp > timestamp + gracePeriod || msg.sender == privilegedProver, + "ZkBobPool: prover is not allowed to submit the proof yet" + ); + } } diff --git a/src/zkbob/manager/AllowListOperatorManager.sol b/src/zkbob/manager/AllowListOperatorManager.sol new file mode 100644 index 0000000..e706787 --- /dev/null +++ b/src/zkbob/manager/AllowListOperatorManager.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.15; + +import "../../interfaces/IOperatorManager.sol"; +import "../../utils/Ownable.sol"; + +/** + * @title AllowListOperatorManager + * @dev Implements a allow list based access control for ZkBobPool relayers. + */ +contract AllowListOperatorManager is IOperatorManager, Ownable { + // if true, only whitelisted addresses can be operators + // if false, anyone can be an operator + bool public allowListEnabled; + + // mapping of whitelisted operator addresses + mapping(address => bool) public operators; + + // mapping of all historical fee receiver addresses, we keep fee receivers addresses + // in a mapping to allow fee withdrawals even after operator address was changed + mapping(address => address) public operatorFeeReceiver; + + event UpdateOperator(address indexed operator, address feeReceiver, bool allowed); + event UpdateAllowListEnabled(bool enabled); + + modifier nonZeroAddress(address addr) { + require(addr != address(0), "WhitelistBasedOperatorManager: zero address"); + _; + } + + constructor( + address[] memory _operators, + bool[] memory _allowed, + address[] memory _feeReceivers, + bool _whitelistEnabled + ) Ownable() { + allowListEnabled = _whitelistEnabled; + _setOperators(_operators, _allowed, _feeReceivers); + } + + function operatorURI() external pure returns (string memory) { + return ""; + } + + function setAllowListEnabled(bool _allowListEnabled) external onlyOwner { + allowListEnabled = _allowListEnabled; + emit UpdateAllowListEnabled(_allowListEnabled); + } + + function setOperators( + address[] calldata _operators, + bool[] calldata _allowed, + address[] calldata _feeReceivers + ) external onlyOwner { + _setOperators(_operators, _allowed, _feeReceivers); + } + + function setOperator( + address _operator, + address _feeReceiver, + bool _allowed + ) external onlyOwner { + _setOperator(_operator, _allowed, _feeReceiver); + } + + function _setOperators( + address[] memory _operators, + bool[] memory _allowed, + address[] memory _feeReceivers + ) internal { + require(_operators.length == _feeReceivers.length, "WhitelistBasedOperatorManager: arrays length mismatch"); + require(_operators.length == _allowed.length, "WhitelistBasedOperatorManager: arrays length mismatch"); + + for (uint256 i = 0; i < _operators.length; i++) { + _setOperator(_operators[i], _allowed[i], _feeReceivers[i]); + } + } + + function _setOperator( + address _operator, + bool _allowed, + address _feeReceiver + ) nonZeroAddress(_operator) internal { + operators[_operator] = _allowed; + if (_feeReceiver != address(0) && _allowed) { + operatorFeeReceiver[_operator] = _feeReceiver; + } + emit UpdateOperator(_operator, _feeReceiver, _allowed); + } + + function isOperator(address _addr) external view override returns (bool) { + return operators[_addr] || !allowListEnabled; + } + + function isOperatorFeeReceiver(address _operator, address _addr) external view override returns (bool) { + return operatorFeeReceiver[_operator] == _addr; + } +} diff --git a/test/interfaces/IZkBobPoolAdmin.sol b/test/interfaces/IZkBobPoolAdmin.sol index 29b9081..11a8b10 100644 --- a/test/interfaces/IZkBobPoolAdmin.sol +++ b/test/interfaces/IZkBobPoolAdmin.sol @@ -25,6 +25,8 @@ interface IZkBobPoolAdmin { function setEnergyRedeemer(IEnergyRedeemer _redeemer) external; + function setMinTreeUpdateFee(uint64 _minTreeUpdateFee) external; + function accounting() external view returns (address); function transact() external; @@ -87,4 +89,6 @@ interface IZkBobPoolAdmin { function direct_deposit_queue() external view returns (address); function pool_id() external view returns (uint256); + + function gracePeriod() external view returns (uint64); } diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 6655a25..746cec3 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -30,7 +30,7 @@ import "../../src/zkbob/ZkBobPoolETH.sol"; import "../../src/infra/UniswapV3Seller.sol"; import {EnergyRedeemer} from "../../src/infra/EnergyRedeemer.sol"; -abstract contract AbstractZkBobPoolTest is AbstractForkTest { +abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { address constant permit2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; address constant uniV3Router = 0xE592427A0AEce92De3Edee1F18E0157C05861564; address constant uniV3Quoter = 0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6; @@ -86,7 +86,7 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { IOperatorManager operatorManager; ZkBobAccounting accounting; - function setUp() public { + function setUp() public virtual { vm.createSelectFork(forkRpcUrl, forkBlock); EIP1967Proxy poolProxy = new EIP1967Proxy(address(this), address(0xdead), ""); @@ -154,6 +154,286 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { deal(token, user3, 0); } + function _setUpDD() internal { + deal(user1, 100 ether / D); + deal(user2, 100 ether / D); + deal(address(token), user1, 100 ether / D); + deal(address(token), user2, 100 ether / D); + + accounting.setLimits( + 1, + 2_000_000 ether / D / denominator, + 200_000 ether / D / denominator, + 200_000 ether / D / denominator, + 20_000 ether / D / denominator, + 20_000 ether / D / denominator, + 25 ether / D / denominator, + 10 ether / D / denominator + ); + address[] memory users = new address[](1); + users[0] = user1; + accounting.setUsersTier(1, users); + + queue.setDirectDepositFee(uint64(0.1 ether / D / pool.denominator())); + + if (autoApproveQueue) { + vm.prank(user1); + IERC20(token).approve(address(queue), type(uint256).max); + vm.prank(user2); + IERC20(token).approve(address(queue), type(uint256).max); + } + } + + function _encodeDeposit(int256 _amount, uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal view returns (bytes memory) { + bytes32 nullifier = bytes32(_randFR()); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk1, ECDSA.toEthSignedMessageHash(nullifier)); + bytes memory data = abi.encodePacked( + ZkBobPool.transact.selector, + nullifier, + _randFR(), + uint48(0), + uint112(0), + int64(_amount / int256(denominator)) + ); + for (uint256 i = 0; i < 8; i++) { + data = abi.encodePacked(data, _randFR()); + } + data = abi.encodePacked( + data, + uint16(0), + uint16(72), + prover, + uint64(_transactFee / denominator), + uint64(_treeUpdateFee / denominator), + bytes4(0x01000000), + _randFR() + ); + return abi.encodePacked(data, r, uint256(s) + (v == 28 ? (1 << 255) : 0)); + } + + function _encodeWithdrawal( + address _to, + uint256 _amount, + uint256 _nativeAmount, + uint256 _energyAmount, + address prover + ) + internal + view + returns (bytes memory) + { + bytes memory data = abi.encodePacked( + ZkBobPool.transact.selector, + _randFR(), + _randFR(), + uint48(0), + -int112(int256(_energyAmount)), + int64(-int256((_amount / denominator) + 0.005 ether / D / denominator + 0.005 ether / D / denominator)) + ); + for (uint256 i = 0; i < 8; i++) { + data = abi.encodePacked(data, _randFR()); + } + + data = abi.encodePacked( + data, + uint16(2), + uint16(100) + ); + + return abi.encodePacked( + data, + prover, + uint64(0.005 ether / D / denominator), + uint64(0.005 ether / D / denominator), + uint64(_nativeAmount / denominator), + _to, + bytes4(0x01000000), + _randFR() + ); + } + + function _encodeTransfer(uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal view returns (bytes memory) { + bytes memory data = abi.encodePacked( + ZkBobPool.transact.selector, _randFR(), _randFR(), uint48(0), uint112(0), -int64(uint64((_transactFee + _treeUpdateFee) / denominator)) + ); + for (uint256 i = 0; i < 8; i++) { + data = abi.encodePacked(data, _randFR()); + } + return abi.encodePacked( + data, + uint16(1), + uint16(72), + prover, + uint64(_transactFee / denominator), + uint64(_treeUpdateFee / denominator), + bytes4(0x01000000), + _randFR() + ); + } + + function _transact(bytes memory _data) internal { + vm.prank(user2); + (bool status,) = address(pool).call(_data); + require(status, "transact() reverted"); + } + + function _proveTreeUpdate() internal { + vm.startPrank(user2); + pool.proveTreeUpdate(pool.pendingCommitment(), _randProof(), _randFR()); + vm.stopPrank(); + } + + function _transactReverted(bytes memory _data, bytes memory _revertReason) internal { + vm.prank(user2); + (bool status, bytes memory returnData) = address(pool).call(_data); + assert(!status); + assertEq(returnData, abi.encodeWithSignature("Error(string)", _revertReason)); + } + + function _randFR() internal view returns (uint256) { + return uint256(keccak256(abi.encode(gasleft()))) + % 21888242871839275222246405745257275088696311157297823662689037894645226208583; + } + + function _randProof() internal view returns (uint256[8] memory) { + return [_randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR()]; + } + + function _encodePermitDeposit(int256 _amount, uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal returns (bytes memory) { + if (permitType == PermitType.Permit2) { + vm.prank(user1); + IERC20(token).approve(permit2, type(uint256).max); + } + + uint256 expiry = block.timestamp + 1 hours; + bytes32 nullifier = bytes32(_randFR()); + + bytes memory signature; + { + bytes32 digest; + if (permitType == PermitType.BOBPermit) { + digest = _digestSaltedPermit(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); + } else if (permitType == PermitType.Permit2) { + digest = _digestPermit2(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); + } else if (permitType == PermitType.USDCPermit) { + digest = _digestUSDCPermit(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); + } + (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk1, digest); + signature = abi.encodePacked(r, uint256(s) + (v == 28 ? (1 << 255) : 0)); + } + + bytes memory data = abi.encodePacked( + ZkBobPool.transact.selector, + nullifier, + _randFR(), + uint48(0), + uint112(0), + int64(_amount / int256(denominator)) + ); + for (uint256 i = 0; i < 8; i++) { + data = abi.encodePacked(data, _randFR()); + } + + data = abi.encodePacked( + data, + uint16(3), + uint16(100) + ); + + data = abi.encodePacked( + data, + prover, + uint64(_transactFee / denominator), + uint64(_treeUpdateFee / denominator), + uint64(expiry), + user1, + bytes4(0x01000000), + _randFR() + ); + return abi.encodePacked(data, signature); + } + + function _digestSaltedPermit( + address _holder, + address _spender, + uint256 _value, + uint256 _expiry, + bytes32 _salt + ) + internal + view + returns (bytes32) + { + uint256 nonce = IERC20Permit(token).nonces(_holder); + return ECDSA.toTypedDataHash( + IERC20Permit(token).DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + IERC20Permit(token).SALTED_PERMIT_TYPEHASH(), _holder, _spender, _value, nonce, _expiry, _salt + ) + ) + ); + } + + function _digestPermit2( + address _holder, + address _spender, + uint256 _value, + uint256 _expiry, + bytes32 _salt + ) + internal + view + returns (bytes32) + { + return ECDSA.toTypedDataHash( + IPermit2(permit2).DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + PERMIT_TRANSFER_FROM_TYPEHASH, + keccak256(abi.encode(TOKEN_PERMISSIONS_TYPEHASH, token, _value)), + _spender, + _salt, + _expiry + ) + ) + ); + } + + function _digestUSDCPermit( + address _holder, + address _spender, + uint256 _value, + uint256 _expiry, + bytes32 _salt + ) + internal + view + returns (bytes32) + { + return ECDSA.toTypedDataHash( + IERC20Permit(token).DOMAIN_SEPARATOR(), + keccak256(abi.encode(TRANSFER_WITH_AUTHORIZATION_TYPEHASH, _holder, _spender, _value, 0, _expiry, _salt)) + ); + } + + function _directDeposit(uint256 amount, address fallbackUser, bytes memory _zkAddress) internal { + if (poolType == PoolType.ETH) { + ZkBobDirectDepositQueueETH(address(queue)).directNativeDeposit{value: amount}(fallbackUser, _zkAddress); + } else if (poolType == PoolType.BOB) { + IERC677(token).transferAndCall(address(queue), amount, abi.encode(fallbackUser, _zkAddress)); + } else { + queue.directDeposit(fallbackUser, amount, _zkAddress); + } + } +} + +abstract contract AbstractZkBobPoolTest is AbstractZkBobPoolTestBase { + + function setUp() public override { + super.setUp(); + } + function testSimpleTransaction() public { bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); _transact(data1); @@ -395,49 +675,19 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { assertNotEq(pool.committedForcedExits(nullifier), hash); } - function testRejectNegativeDeposits() public { - bytes memory data1 = _encodePermitDeposit(int256(0.99 ether / D), 0.005 ether / D, 0.005 ether / D, user2); - _transact(data1); - _proveTreeUpdate(); - - bytes memory data2 = _encodePermitDeposit(-int256(0.5 ether / D), 0.5 ether / D, 0.5 ether / D, user2); - _transactReverted(data2, "ZkBobPool: incorrect deposit amounts"); - - vm.prank(user1); - IERC20(token).approve(address(pool), 0.5 ether / D); - - bytes memory data3 = _encodeDeposit(-int256(0.5 ether / D), 0.5 ether / D, 0.5 ether / D, user2); - _transactReverted(data3, "ZkBobPool: incorrect deposit amounts"); - } - - function _setUpDD() internal { - deal(user1, 100 ether / D); - deal(user2, 100 ether / D); - deal(address(token), user1, 100 ether / D); - deal(address(token), user2, 100 ether / D); - - accounting.setLimits( - 1, - 2_000_000 ether / D / denominator, - 200_000 ether / D / denominator, - 200_000 ether / D / denominator, - 20_000 ether / D / denominator, - 20_000 ether / D / denominator, - 25 ether / D / denominator, - 10 ether / D / denominator - ); - address[] memory users = new address[](1); - users[0] = user1; - accounting.setUsersTier(1, users); + function testRejectNegativeDeposits() public { + bytes memory data1 = _encodePermitDeposit(int256(0.99 ether / D), 0.005 ether / D, 0.005 ether / D, user2); + _transact(data1); + _proveTreeUpdate(); - queue.setDirectDepositFee(uint64(0.1 ether / D / pool.denominator())); + bytes memory data2 = _encodePermitDeposit(-int256(0.5 ether / D), 0.5 ether / D, 0.5 ether / D, user2); + _transactReverted(data2, "ZkBobPool: incorrect deposit amounts"); - if (autoApproveQueue) { - vm.prank(user1); - IERC20(token).approve(address(queue), type(uint256).max); - vm.prank(user2); - IERC20(token).approve(address(queue), type(uint256).max); - } + vm.prank(user1); + IERC20(token).approve(address(pool), 0.5 ether / D); + + bytes memory data3 = _encodeDeposit(-int256(0.5 ether / D), 0.5 ether / D, 0.5 ether / D, user2); + _transactReverted(data3, "ZkBobPool: incorrect deposit amounts"); } function testDirectDepositSubmit() public { @@ -742,248 +992,6 @@ abstract contract AbstractZkBobPoolTest is AbstractForkTest { // 1e18 energy * (1e16 * 1e12 / 1e18) / 2e5 / 28 ~= 1785e18 reward tokens assertApproxEqAbs(rewardToken.balanceOf(user1), 1785 ether, 200 ether); } - - function _encodeDeposit(int256 _amount, uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal returns (bytes memory) { - bytes32 nullifier = bytes32(_randFR()); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk1, ECDSA.toEthSignedMessageHash(nullifier)); - bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, - nullifier, - _randFR(), - uint48(0), - uint112(0), - int64(_amount / int256(denominator)) - ); - for (uint256 i = 0; i < 8; i++) { - data = abi.encodePacked(data, _randFR()); - } - data = abi.encodePacked( - data, - uint16(0), - uint16(72), - prover, - uint64(_transactFee / denominator), - uint64(_treeUpdateFee / denominator), - bytes4(0x01000000), - _randFR() - ); - return abi.encodePacked(data, r, uint256(s) + (v == 28 ? (1 << 255) : 0)); - } - - function _encodeWithdrawal( - address _to, - uint256 _amount, - uint256 _nativeAmount, - uint256 _energyAmount, - address prover - ) - internal - returns (bytes memory) - { - bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, - _randFR(), - _randFR(), - uint48(0), - -int112(int256(_energyAmount)), - int64(-int256((_amount / denominator) + 0.005 ether / D / denominator + 0.005 ether / D / denominator)) - ); - for (uint256 i = 0; i < 8; i++) { - data = abi.encodePacked(data, _randFR()); - } - - data = abi.encodePacked( - data, - uint16(2), - uint16(100) - ); - - return abi.encodePacked( - data, - prover, - uint64(0.005 ether / D / denominator), - uint64(0.005 ether / D / denominator), - uint64(_nativeAmount / denominator), - _to, - bytes4(0x01000000), - _randFR() - ); - } - - function _encodeTransfer(uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal returns (bytes memory) { - bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, _randFR(), _randFR(), uint48(0), uint112(0), -int64(uint64((_transactFee + _treeUpdateFee) / denominator)) - ); - for (uint256 i = 0; i < 8; i++) { - data = abi.encodePacked(data, _randFR()); - } - return abi.encodePacked( - data, - uint16(1), - uint16(72), - prover, - uint64(_transactFee / denominator), - uint64(_treeUpdateFee / denominator), - bytes4(0x01000000), - _randFR() - ); - } - - function _transact(bytes memory _data) internal { - vm.prank(user2); - (bool status,) = address(pool).call(_data); - require(status, "transact() reverted"); - } - - function _proveTreeUpdate() internal { - vm.startPrank(user2); - pool.proveTreeUpdate(pool.pendingCommitment(), _randProof(), _randFR()); - vm.stopPrank(); - } - - function _transactReverted(bytes memory _data, bytes memory _revertReason) internal { - vm.prank(user2); - (bool status, bytes memory returnData) = address(pool).call(_data); - assert(!status); - assertEq(returnData, abi.encodeWithSignature("Error(string)", _revertReason)); - } - - function _randFR() internal returns (uint256) { - return uint256(keccak256(abi.encode(gasleft()))) - % 21888242871839275222246405745257275088696311157297823662689037894645226208583; - } - - function _randProof() internal returns (uint256[8] memory) { - return [_randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR()]; - } - - function _encodePermitDeposit(int256 _amount, uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal returns (bytes memory) { - if (permitType == PermitType.Permit2) { - vm.prank(user1); - IERC20(token).approve(permit2, type(uint256).max); - } - - uint256 expiry = block.timestamp + 1 hours; - bytes32 nullifier = bytes32(_randFR()); - - bytes memory signature; - { - bytes32 digest; - if (permitType == PermitType.BOBPermit) { - digest = _digestSaltedPermit(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); - } else if (permitType == PermitType.Permit2) { - digest = _digestPermit2(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); - } else if (permitType == PermitType.USDCPermit) { - digest = _digestUSDCPermit(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); - } - (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk1, digest); - signature = abi.encodePacked(r, uint256(s) + (v == 28 ? (1 << 255) : 0)); - } - - bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, - nullifier, - _randFR(), - uint48(0), - uint112(0), - int64(_amount / int256(denominator)) - ); - for (uint256 i = 0; i < 8; i++) { - data = abi.encodePacked(data, _randFR()); - } - - data = abi.encodePacked( - data, - uint16(3), - uint16(100) - ); - - data = abi.encodePacked( - data, - prover, - uint64(_transactFee / denominator), - uint64(_treeUpdateFee / denominator), - uint64(expiry), - user1, - bytes4(0x01000000), - _randFR() - ); - return abi.encodePacked(data, signature); - } - - function _digestSaltedPermit( - address _holder, - address _spender, - uint256 _value, - uint256 _expiry, - bytes32 _salt - ) - internal - view - returns (bytes32) - { - uint256 nonce = IERC20Permit(token).nonces(_holder); - return ECDSA.toTypedDataHash( - IERC20Permit(token).DOMAIN_SEPARATOR(), - keccak256( - abi.encode( - IERC20Permit(token).SALTED_PERMIT_TYPEHASH(), _holder, _spender, _value, nonce, _expiry, _salt - ) - ) - ); - } - - function _digestPermit2( - address _holder, - address _spender, - uint256 _value, - uint256 _expiry, - bytes32 _salt - ) - internal - view - returns (bytes32) - { - return ECDSA.toTypedDataHash( - IPermit2(permit2).DOMAIN_SEPARATOR(), - keccak256( - abi.encode( - PERMIT_TRANSFER_FROM_TYPEHASH, - keccak256(abi.encode(TOKEN_PERMISSIONS_TYPEHASH, token, _value)), - _spender, - _salt, - _expiry - ) - ) - ); - } - - function _digestUSDCPermit( - address _holder, - address _spender, - uint256 _value, - uint256 _expiry, - bytes32 _salt - ) - internal - view - returns (bytes32) - { - return ECDSA.toTypedDataHash( - IERC20Permit(token).DOMAIN_SEPARATOR(), - keccak256(abi.encode(TRANSFER_WITH_AUTHORIZATION_TYPEHASH, _holder, _spender, _value, 0, _expiry, _salt)) - ); - } - - function _directDeposit(uint256 amount, address fallbackUser, bytes memory _zkAddress) internal { - if (poolType == PoolType.ETH) { - ZkBobDirectDepositQueueETH(address(queue)).directNativeDeposit{value: amount}(fallbackUser, _zkAddress); - } else if (poolType == PoolType.BOB) { - IERC677(token).transferAndCall(address(queue), amount, abi.encode(fallbackUser, _zkAddress)); - } else { - queue.directDeposit(fallbackUser, amount, _zkAddress); - } - } } contract ZkBobPoolBOBPolygonTest is AbstractZkBobPoolTest, AbstractPolygonForkTest { diff --git a/test/zkbob/ZkBobPoolDecentralized.t.sol b/test/zkbob/ZkBobPoolDecentralized.t.sol new file mode 100644 index 0000000..5af9dae --- /dev/null +++ b/test/zkbob/ZkBobPoolDecentralized.t.sol @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.15; + +import {AbstractZkBobPoolTestBase} from "./ZkBobPool.t.sol"; +import {AllowListOperatorManager} from "../../src/zkbob/manager/AllowListOperatorManager.sol"; +import {IOperatorManager} from "../../src/interfaces/IOperatorManager.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "../shared/ForkTests.t.sol"; + +abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBase { + AllowListOperatorManager manager; + + address prover1 = makeAddr("Prover #1"); + address feeReceiver1 = makeAddr("Fee Receiver #1"); + + address prover2 = makeAddr("Prover #2"); + address feeReceiver2 = makeAddr("Fee Receiver #2"); + + address notAllowedProver = makeAddr("Not Allowed Prover"); + + address[] provers = [prover1, prover2]; + address[] feeReceivers = [feeReceiver1, feeReceiver2]; + bool[] allowed = [true, true]; + + function setUp() public override { + super.setUp(); + + manager = new AllowListOperatorManager(provers, allowed, feeReceivers, true); + pool.setOperatorManager(IOperatorManager(manager)); + } + + function testOnlyAllowedProversCanTransact() public { + deal(token, user1, 100 ether / D); + + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); + _transact(data1, prover1); + + bytes memory data2 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover2); + _transact(data2, prover2); + + bytes memory data3 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, notAllowedProver); + _transactExpectRevert(data3, notAllowedProver, "ZkBobPool: not an operator"); + + manager.setAllowListEnabled(false); + _transact(data3, notAllowedProver); + } + + function testOnlyPrivilegedProverCanUpdateTreeWithinGracePeriod() public { + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); + _transact(data1, prover1); + + vm.warp(block.timestamp + pool.gracePeriod()); + + _proveTreeUpdateExpectRevert(prover2, "ZkBobPool: prover is not allowed to submit the proof yet"); + + _proveTreeUpdate(prover1); + } + + function testAnyAllowedProverCanUpdateTreeAfterGracePeriod() public { + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); + _transact(data1, prover1); + + vm.warp(block.timestamp + pool.gracePeriod() + 1); + + _proveTreeUpdate(prover2); + } + + function testNotAllowedProverCantUpdateTreeEvenAfterGracePeriod() public { + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); + _transact(data1, prover1); + + vm.warp(block.timestamp + pool.gracePeriod() + 1); + + _proveTreeUpdateExpectRevert(notAllowedProver, "ZkBobPool: not an operator"); + } + + function testGracePeriodStartsOnlyAfterTreeUpdatingIfNewTransactSendedBeforeOldTreeUpdate() public { + deal(token, user1, 100 ether / D); + + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); + _transact(data1, prover1); + + bytes memory data2 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover2); + _transact(data2, prover2); + + vm.warp(block.timestamp + pool.gracePeriod()); + _proveTreeUpdate(prover1); + + vm.warp(block.timestamp + pool.gracePeriod()); + _proveTreeUpdate(prover2); + } + + function testFeeDistribution() public { + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.017 ether / D, 0.005 ether / D, prover1); + _transact(data1, prover1); + assertEq(pool.accumulatedFee(prover1), 0.017 ether / (D * denominator)); + + vm.prank(feeReceiver1); + pool.withdrawFee(prover1, feeReceiver1); + + vm.warp(block.timestamp + pool.gracePeriod() + 1); + + _proveTreeUpdate(prover2); + assertEq(pool.accumulatedFee(prover2), 0.005 ether / (D * denominator)); + + vm.prank(feeReceiver2); + pool.withdrawFee(prover2, feeReceiver2); + + assertEq(pool.accumulatedFee(prover1), 0); + assertEq(pool.accumulatedFee(prover2), 0); + + assertEq(IERC20(token).balanceOf(feeReceiver1), 0.017 ether / D); + assertEq(IERC20(token).balanceOf(feeReceiver2), 0.005 ether / D); + assertEq(IERC20(token).balanceOf(address(pool)), 0.5 ether / D); + assertEq(IERC20(token).balanceOf(user1), 0.478 ether / D); // user1 has 1 ether before the deposit + } + + function testCantSkipCommitments() public { + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); + _transact(data1, prover1); + + vm.expectRevert("ZkBobPool: commitment mismatch"); + vm.prank(prover1); + pool.proveTreeUpdate(_randFR(), _randProof(), _randFR()); + } + + function testCantTransactIfTreeUpdateFeeIsLessThenMin() public { + deal(token, user1, 100 ether / D); + pool.setMinTreeUpdateFee(uint64(0.01 ether / (D * denominator))); + + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.009 ether / D, prover1); + _transactExpectRevert(data1, prover1, "ZkBobPool: tree update fee is too low"); + + bytes memory data2 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.01 ether / D, prover1); + _transact(data2, prover1); + + bytes memory data3 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.011 ether / D, prover1); + _transact(data3, prover1); + } + + function _transact(bytes memory _data, address caller) internal { + vm.prank(caller); + (bool status,) = address(pool).call(_data); + assertTrue(status); + } + + function _transactExpectRevert(bytes memory _data, address caller, string memory expectedRevertReason) internal { + vm.prank(caller); + (bool status, bytes memory data) = address(pool).call(_data); + assertFalse(status); + assembly { + data := add(data, 0x04) + } + bytes memory revertReason = abi.decode(data, (bytes)); + assertEq(revertReason, bytes(expectedRevertReason)); + } + + function _proveTreeUpdate(address caller) internal { + uint256 commitment = pool.pendingCommitment(); + vm.prank(caller); + pool.proveTreeUpdate(commitment, _randProof(), _randFR()); + } + + function _proveTreeUpdateExpectRevert(address caller, string memory expectedRevertReason) internal { + uint256 commitment = pool.pendingCommitment(); + vm.expectRevert(bytes(expectedRevertReason)); + vm.prank(caller); + pool.proveTreeUpdate(commitment, _randProof(), _randFR()); + } +} + +contract ZkBobPoolBOBPolygonDecentralizedTest is AbstractZkBobPoolDecentralizedTest, AbstractPolygonForkTest { + constructor() { + D = 1; + token = address(0xB0B195aEFA3650A6908f15CdaC7D92F8a5791B0B); + weth = address(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270); + tempToken = address(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174); + poolType = PoolType.BOB; + autoApproveQueue = false; + permitType = PermitType.BOBPermit; + denominator = 1_000_000_000; + precision = 1_000_000_000; + } +} + +contract ZkBobPoolETHMainnetDecentralizedTest is AbstractZkBobPoolDecentralizedTest, AbstractMainnetForkTest { + constructor() { + D = 1; + token = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + weth = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + tempToken = address(0); + poolType = PoolType.ETH; + autoApproveQueue = false; + permitType = PermitType.Permit2; + denominator = 1_000_000_000; + precision = 1_000_000_000; + } +} + +contract ZkBobPoolDAIMainnetDecentralizedTest is AbstractZkBobPoolDecentralizedTest, AbstractMainnetForkTest { + constructor() { + D = 1; + token = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); + weth = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + tempToken = address(0); + poolType = PoolType.ERC20; + autoApproveQueue = true; + permitType = PermitType.Permit2; + denominator = 1_000_000_000; + precision = 1_000_000_000; + } +} + +contract ZkBobPoolUSDCPolygonDecentralizedTest is AbstractZkBobPoolDecentralizedTest, AbstractPolygonForkTest { + constructor() { + D = 10 ** 12; + token = address(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174); + weth = address(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270); + tempToken = address(0); + poolType = PoolType.USDC; + autoApproveQueue = true; + permitType = PermitType.USDCPermit; + denominator = 1; + precision = 1_000_000; + } +} \ No newline at end of file From 1da8886b9d9b200ee2896f455d28a5c46a268916 Mon Sep 17 00:00:00 2001 From: vladimir Date: Thu, 30 Nov 2023 12:22:13 +0400 Subject: [PATCH 04/40] adds tests for dd --- test/zkbob/ZkBobPool.t.sol | 3 ++ test/zkbob/ZkBobPoolDecentralized.t.sol | 50 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 746cec3..203f5ab 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -28,6 +28,7 @@ import "../../src/zkbob/ZkBobPoolERC20.sol"; import "../../src/zkbob/ZkBobPoolBOB.sol"; import "../../src/zkbob/ZkBobPoolETH.sol"; import "../../src/infra/UniswapV3Seller.sol"; +import "../../src/utils/Ownable.sol"; import {EnergyRedeemer} from "../../src/infra/EnergyRedeemer.sol"; abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { @@ -152,6 +153,8 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { deal(token, user1, 1 ether / D); deal(token, user3, 0); + ZkBobPool(address(poolProxy)).transferOwnership(owner); + ZkBobPool(address(queueProxy)).transferOwnership(owner); } function _setUpDD() internal { diff --git a/test/zkbob/ZkBobPoolDecentralized.t.sol b/test/zkbob/ZkBobPoolDecentralized.t.sol index 5af9dae..8818524 100644 --- a/test/zkbob/ZkBobPoolDecentralized.t.sol +++ b/test/zkbob/ZkBobPoolDecentralized.t.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.15; import {AbstractZkBobPoolTestBase} from "./ZkBobPool.t.sol"; import {AllowListOperatorManager} from "../../src/zkbob/manager/AllowListOperatorManager.sol"; import {IOperatorManager} from "../../src/interfaces/IOperatorManager.sol"; +import {IBatchDepositVerifier} from "../../src/interfaces/IBatchDepositVerifier.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../shared/ForkTests.t.sol"; @@ -168,6 +169,55 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas vm.prank(caller); pool.proveTreeUpdate(commitment, _randProof(), _randFR()); } + + function testDirectDepositsTreeUpdateFeeTooLow() public { + _setUpDD(); + + vm.startPrank(owner); + + pool.setMinTreeUpdateFee(uint64(3)); + queue.setDirectDepositFee(uint64(1)); + + vm.startPrank(user1); + + _directDeposit(10 ether / D, user2, zkAddress); + _directDeposit(5 ether / D, user2, zkAddress); + vm.stopPrank(); + + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 1; + address verifier = address(pool.batch_deposit_verifier()); + uint256 outCommitment = _randFR(); + vm.prank(prover1); + vm.expectRevert("ZkBobPool: tree update fee is too low"); + pool.appendDirectDeposits(indices, outCommitment, _randProof()); + } + function testDirectDepositsTreeFeesAccrued() public { + _setUpDD(); + + uint64 minTreeUpdateFee = uint64(0.01 ether / (D * denominator)); + uint64 singleDirectDepositFee = uint64(0.1 ether / (D * denominator)); + + vm.startPrank(owner); + pool.setMinTreeUpdateFee(minTreeUpdateFee); + queue.setDirectDepositFee(singleDirectDepositFee); + + vm.startPrank(user1); + _directDeposit(10 ether / D, user2, zkAddress); + _directDeposit(5 ether / D, user2, zkAddress); + vm.stopPrank(); + + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 1; + address verifier = address(pool.batch_deposit_verifier()); + uint256 outCommitment = _randFR(); + vm.prank(prover1); + pool.appendDirectDeposits(indices, outCommitment, _randProof()); + uint64 expectedFee = uint64(singleDirectDepositFee* 2 - minTreeUpdateFee); + assertEq(expectedFee, pool.accumulatedFee(prover1)); + } } contract ZkBobPoolBOBPolygonDecentralizedTest is AbstractZkBobPoolDecentralizedTest, AbstractPolygonForkTest { From effa3ee9174826790ccbe04c01532b6c980246a8 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Thu, 30 Nov 2023 17:49:15 +0300 Subject: [PATCH 05/40] Fix message event, add comments --- src/zkbob/ZkBobPool.sol | 52 ++++++++++++++---- test/interfaces/IZkBobPoolAdmin.sol | 2 + test/zkbob/ZkBobPool.t.sol | 8 +-- test/zkbob/ZkBobPoolDecentralized.t.sol | 73 ++++++++++++++++++++++++- 4 files changed, 116 insertions(+), 19 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 1811b1b..9d38ba8 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -66,7 +66,17 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex PriorityQueue.Queue internal pendingCommitments; uint64 internal lastTreeUpdateTimestamp; + /** + * @dev The duration of the grace period within which only the prover who submitted the transaction + * can submit the tree update proof. + */ uint64 public gracePeriod = 5 minutes; + + /** + * @dev The minimal fee required to be reserved for the prover who will submit the tree update proof. + * This fee is used to prevent spamming the pool with transactions that don't incentivize provers + * to provide tree update proof. + */ uint64 public minTreeUpdateFee = 0; event UpdateOperatorManager(address manager); @@ -109,10 +119,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex direct_deposit_queue = IZkBobDirectDepositQueue(_direct_deposit_queue); TOKEN_DENOMINATOR = _denominator; - - // TODO: Hardcoded values for now - gracePeriod = 5 minutes; - minTreeUpdateFee = 0; } /** @@ -192,11 +198,21 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex emit UpdateRedeemer(address(_redeemer)); } + /** + * @dev Updates grace period duration. + * Callable only by the contract owner / proxy admin. + * @param _gracePeriod new grace period duration. + */ function setGracePeriod(uint64 _gracePeriod) external onlyOwner { gracePeriod = _gracePeriod; emit UpdateGracePeriod(_gracePeriod); } + /** + * @dev Updates minimal fee required to be reserved for the prover who will submit the tree update proof. + * Callable only by the contract owner / proxy admin. + * @param _minTreeUpdateFee new minimal fee. + */ function setMinTreeUpdateFee(uint64 _minTreeUpdateFee) external onlyOwner { minTreeUpdateFee = _minTreeUpdateFee; emit UpdateMinTreeUpdateFee(_minTreeUpdateFee); @@ -272,15 +288,17 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex bytes32 message_hash = keccak256(message); bytes32 _all_messages_hash = keccak256(abi.encodePacked(all_messages_hash, message_hash)); all_messages_hash = _all_messages_hash; - pool_index = poolIndex; - // TODO: is it fine that index wasn't updated? - emit Message(poolIndex, _all_messages_hash, message); + // Since there is no way to skip commitments in the queue + // we can precompute the index of this commitment in the merkle tree + uint256 pendingIndex = poolIndex + 128 * pendingCommitments.getSize(); + emit Message(pendingIndex, _all_messages_hash, message); } uint256 transactFee = _memo_transact_fee(); uint256 treeUpdateFee = _memo_tree_update_fee(); + require(minTreeUpdateFee > 0, "ZkBobPool: minimal tree update fee is not set"); require(treeUpdateFee >= minTreeUpdateFee, "ZkBobPool: tree update fee is too low"); uint256 fee = transactFee + treeUpdateFee; @@ -359,21 +377,24 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex ); // TODO: is it ok? - require(totalFee > minTreeUpdateFee, "ZkBobPool: tree update fee is too low"); + require(minTreeUpdateFee > 0, "ZkBobPool: minimal tree update fee is not set"); + require(totalFee >= minTreeUpdateFee, "ZkBobPool: tree update fee is too low"); uint64 ddFee = uint64(totalFee) - minTreeUpdateFee; + _appendCommitment(_out_commit, minTreeUpdateFee, msg.sender); bytes32 message_hash = keccak256(message); bytes32 _all_messages_hash = keccak256(abi.encodePacked(all_messages_hash, message_hash)); all_messages_hash = _all_messages_hash; - pool_index = poolIndex; - if (totalFee > 0) { + if (ddFee > 0) { accumulatedFee[msg.sender] += ddFee; } - // TODO: is it fine that index wasn't updated? - emit Message(poolIndex, _all_messages_hash, message); + // Since there is no way to skip commitments in the queue + // we can precompute the index of this commitment in the merkle tree + uint256 pendingIndex = poolIndex + 128 * pendingCommitments.getSize(); + emit Message(pendingIndex, _all_messages_hash, message); } /** @@ -579,6 +600,9 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex return super._isOwner() || _admin() == _msgSender(); } + /** + * @dev Appends a commitment to the pending commitments queue. + */ function _appendCommitment(uint256 _commitment, uint64 _fee, address _prover) internal { pendingCommitments.pushBack(PriorityOperation({ commitment: _commitment, @@ -588,6 +612,10 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex })); } + /** + * @dev Validates either the grace period has passed or the caller + * is the prover who submitted this commitment. + */ function _validateGracePeriod(uint64 commitmentTimestamp, address privilegedProver) internal view { // We calculate the beggining of the grace period either from the timestamp of the last tree update, // or from the timestamp of the commitment, whichever is greater. diff --git a/test/interfaces/IZkBobPoolAdmin.sol b/test/interfaces/IZkBobPoolAdmin.sol index 11a8b10..ff35cc4 100644 --- a/test/interfaces/IZkBobPoolAdmin.sol +++ b/test/interfaces/IZkBobPoolAdmin.sol @@ -25,6 +25,8 @@ interface IZkBobPoolAdmin { function setEnergyRedeemer(IEnergyRedeemer _redeemer) external; + function setGracePeriod(uint64 _gracePeriod) external; + function setMinTreeUpdateFee(uint64 _minTreeUpdateFee) external; function accounting() external view returns (address); diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 203f5ab..171a008 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -28,7 +28,6 @@ import "../../src/zkbob/ZkBobPoolERC20.sol"; import "../../src/zkbob/ZkBobPoolBOB.sol"; import "../../src/zkbob/ZkBobPoolETH.sol"; import "../../src/infra/UniswapV3Seller.sol"; -import "../../src/utils/Ownable.sol"; import {EnergyRedeemer} from "../../src/infra/EnergyRedeemer.sol"; abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { @@ -151,10 +150,11 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { queue.setDirectDepositFee(uint64(0.1 ether / D)); queue.setDirectDepositTimeout(1 days); + pool.setGracePeriod(5 minutes); + pool.setMinTreeUpdateFee(1); + deal(token, user1, 1 ether / D); deal(token, user3, 0); - ZkBobPool(address(poolProxy)).transferOwnership(owner); - ZkBobPool(address(queueProxy)).transferOwnership(owner); } function _setUpDD() internal { @@ -783,7 +783,7 @@ abstract contract AbstractZkBobPoolTest is AbstractZkBobPoolTestBase { uint64(4.9 ether / D / denominator) // second deposit amount ); vm.expectEmit(true, false, false, true); - emit Message(0, bytes32(0), message); + emit Message(128, bytes32(0), message); vm.prank(user2); pool.appendDirectDeposits(indices, outCommitment, _randProof()); } diff --git a/test/zkbob/ZkBobPoolDecentralized.t.sol b/test/zkbob/ZkBobPoolDecentralized.t.sol index 8818524..80f2744 100644 --- a/test/zkbob/ZkBobPoolDecentralized.t.sol +++ b/test/zkbob/ZkBobPoolDecentralized.t.sol @@ -140,6 +140,45 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas _transact(data3, prover1); } + function testIndexInMessageEventIsConstructedCorrectly() public { + uint256 index = pool.pool_index(); + deal(token, user1, 100 ether / D); + + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); + vm.expectEmit(true, false, false, false); + bytes memory message = new bytes(0); + emit Message(index + 128, bytes32(0), message); + _transact(data1, prover1); + + bytes memory data2 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover2); + vm.expectEmit(true, false, false, false); + emit Message(index + 2 * 128, bytes32(0), message); + _transact(data2, prover2); + + (uint256[] memory indices, uint256 commitment, uint256[8] memory proof) = _prepareRandomDirectDeposits(0); + vm.expectEmit(true, false, false, false); + emit Message(index + 3 * 128, bytes32(0), message); + _appendDirectDeposits(indices, commitment, proof, prover1); + + _proveTreeUpdate(prover1); + _proveTreeUpdate(prover2); + + bytes memory data3 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover2); + vm.expectEmit(true, false, false, false); + emit Message(index + 4 * 128, bytes32(0), message); + _transact(data3, prover2); + + _proveTreeUpdate(prover1); + _proveTreeUpdate(prover2); + + (indices, commitment, proof) = _prepareRandomDirectDeposits(2); + vm.expectEmit(true, false, false, false); + emit Message(index + 5 * 128, bytes32(0), message); + _appendDirectDeposits(indices, commitment, proof, prover2); + + _proveTreeUpdate(prover2); + } + function _transact(bytes memory _data, address caller) internal { vm.prank(caller); (bool status,) = address(pool).call(_data); @@ -170,10 +209,38 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas pool.proveTreeUpdate(commitment, _randProof(), _randFR()); } - function testDirectDepositsTreeUpdateFeeTooLow() public { + function _prepareRandomDirectDeposits(uint256 offset) internal returns ( + uint256[] memory indices, + uint256 commitment, + uint256[8] memory proof + ) { _setUpDD(); - vm.startPrank(owner); + vm.startPrank(user1); + _directDeposit(1 ether / D, user2, zkAddress); + _directDeposit(1 ether / D, user2, zkAddress); + vm.stopPrank(); + + indices = new uint256[](2); + indices[0] = offset + 0; + indices[1] = offset + 1; + + commitment = _randFR(); + proof = _randProof(); + } + + function _appendDirectDeposits( + uint256[] memory indices, + uint256 commitment, + uint256[8] memory proof, + address prover + ) internal { + vm.prank(prover); + pool.appendDirectDeposits(indices, commitment, proof); + } + + function testDirectDepositsTreeUpdateFeeTooLow() public { + _setUpDD(); pool.setMinTreeUpdateFee(uint64(3)); queue.setDirectDepositFee(uint64(1)); @@ -193,13 +260,13 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas vm.expectRevert("ZkBobPool: tree update fee is too low"); pool.appendDirectDeposits(indices, outCommitment, _randProof()); } + function testDirectDepositsTreeFeesAccrued() public { _setUpDD(); uint64 minTreeUpdateFee = uint64(0.01 ether / (D * denominator)); uint64 singleDirectDepositFee = uint64(0.1 ether / (D * denominator)); - vm.startPrank(owner); pool.setMinTreeUpdateFee(minTreeUpdateFee); queue.setDirectDepositFee(singleDirectDepositFee); From 799b765ee6df29554e60a4f796c9bd998047196d Mon Sep 17 00:00:00 2001 From: vladimir Date: Fri, 1 Dec 2023 11:13:00 +0400 Subject: [PATCH 06/40] adds tests for priority queue --- src/zkbob/utils/PriorityQueue.sol | 8 +++ test/libraries/PriorityQueue.t.sol | 83 ++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 test/libraries/PriorityQueue.t.sol diff --git a/src/zkbob/utils/PriorityQueue.sol b/src/zkbob/utils/PriorityQueue.sol index 8d26c1e..1935731 100644 --- a/src/zkbob/utils/PriorityQueue.sol +++ b/src/zkbob/utils/PriorityQueue.sol @@ -61,6 +61,14 @@ library PriorityQueue { _queue.tail = tail + 1; } + function list(Queue storage _queue) external view returns ( PriorityOperation[] memory) { + PriorityOperation[] memory result = new PriorityOperation[] (_queue.getSize()); + for (uint256 index = _queue.head; index < _queue.tail; index++) { + result[index-_queue.head] = _queue.data[index]; + } + return result; + } + /// @return The first unprocessed priority operation from the queue function front(Queue storage _queue) internal view returns (PriorityOperation memory) { require(!_queue.isEmpty(), "D"); // priority queue is empty diff --git a/test/libraries/PriorityQueue.t.sol b/test/libraries/PriorityQueue.t.sol new file mode 100644 index 0000000..6f3076c --- /dev/null +++ b/test/libraries/PriorityQueue.t.sol @@ -0,0 +1,83 @@ +pragma solidity ^0.8.15; +import "forge-std/Test.sol"; +import "../../src/zkbob/utils/PriorityQueue.sol"; +import "forge-std/console.sol"; + +contract DummyQueue { + using PriorityQueue for PriorityQueue.Queue; + + PriorityQueue.Queue _queue; + address immutable prover1 = address(bytes20(new bytes(20))); + + function list() external view returns (PriorityOperation[] memory) { + return _queue.list(); + } + + function pushBack(PriorityOperation memory _operation) external { + _queue.pushBack(_operation); + } + + function head() external view returns (uint256) { + return _queue.head; + } + + function tail() external view returns (uint256) { + return _queue.tail; + } + + function popFront() external returns (PriorityOperation memory priorityOperation) { + return _queue.popFront(); + } +} + +contract PriorityQueueTest is Test { + using PriorityQueue for PriorityQueue.Queue; + + address immutable prover1 = makeAddr("Prover #1"); + DummyQueue _queueContract; + + function setUp() external { + _queueContract = new DummyQueue(); + } + + function newOp( + uint256 id + ) external view returns (PriorityOperation memory) { + return PriorityOperation(id, prover1, uint64(0), uint64(0)); + } + + function testEmptyQueue() external { + PriorityOperation[] memory ops = _queueContract.list(); + assertEq(0, ops.length); + } + + function testPushBack() external { + _queueContract.pushBack(this.newOp(0)); + + assertEq(0, _queueContract.head()); + assertEq(1, _queueContract.tail()); + + assertEq(1, _queueContract.list().length); + + _queueContract.pushBack(this.newOp(2)); + + assertEq(2, _queueContract.list().length); + } + function testPopFront() external { + _queueContract.pushBack(this.newOp(0)); + _queueContract.pushBack(this.newOp(1)); + _queueContract.pushBack(this.newOp(2)); + + assertEq(0, _queueContract.head()); + assertEq(3, _queueContract.tail()); + + PriorityOperation memory first = _queueContract.popFront(); + + assertEq(first.commitment, uint256(0)); + + assertEq(1, _queueContract.head()); + assertEq(3, _queueContract.tail()); + + assertEq(2, _queueContract.list().length); + } +} From cccd9e7307b3c8e43dbbd596950346d1bb3c40c7 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Mon, 11 Dec 2023 12:17:52 +0300 Subject: [PATCH 07/40] Add tests, minor fixes --- src/zkbob/ZkBobPool.sol | 12 +- .../manager/AllowListOperatorManager.sol | 15 ++- test/interfaces/IZkBobPoolAdmin.sol | 2 +- test/zkbob/ZkBobPool.t.sol | 3 +- test/zkbob/ZkBobPoolDecentralized.t.sol | 112 ++++++++++-------- .../manager/AllowListOperatorManager.t.sol | 62 ++++++++++ 6 files changed, 144 insertions(+), 62 deletions(-) create mode 100644 test/zkbob/manager/AllowListOperatorManager.t.sol diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 9d38ba8..f0a1204 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -168,10 +168,15 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex return TOKEN_NUMERATOR == 1 ? TOKEN_DENOMINATOR : (1 << 255) | TOKEN_NUMERATOR; } - function pendingCommitment() external view returns (uint256 commitment) { + function pendingCommitment() external view returns ( + uint256 commitment, + address privilegedProver, + uint64 timestamp, + uint64 fee + ) { PriorityOperation memory op = pendingCommitments.front(); require(op.commitment != 0, "ZkBobPool: no pending commitment"); - return op.commitment; + return (op.commitment, op.prover, op.timestamp, op.fee); } /** @@ -214,6 +219,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex * @param _minTreeUpdateFee new minimal fee. */ function setMinTreeUpdateFee(uint64 _minTreeUpdateFee) external onlyOwner { + require(_minTreeUpdateFee > 0, "ZkBobPool: tree update fee can't be zero"); minTreeUpdateFee = _minTreeUpdateFee; emit UpdateMinTreeUpdateFee(_minTreeUpdateFee); } @@ -376,7 +382,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex batch_deposit_verifier.verifyProof([hashsum], _batch_deposit_proof), "ZkBobPool: bad batch deposit proof" ); - // TODO: is it ok? + // we reserve the minimal tree update fee for the prover who will submit the tree update proof require(minTreeUpdateFee > 0, "ZkBobPool: minimal tree update fee is not set"); require(totalFee >= minTreeUpdateFee, "ZkBobPool: tree update fee is too low"); uint64 ddFee = uint64(totalFee) - minTreeUpdateFee; diff --git a/src/zkbob/manager/AllowListOperatorManager.sol b/src/zkbob/manager/AllowListOperatorManager.sol index e706787..7164e21 100644 --- a/src/zkbob/manager/AllowListOperatorManager.sol +++ b/src/zkbob/manager/AllowListOperatorManager.sol @@ -25,17 +25,20 @@ contract AllowListOperatorManager is IOperatorManager, Ownable { event UpdateAllowListEnabled(bool enabled); modifier nonZeroAddress(address addr) { - require(addr != address(0), "WhitelistBasedOperatorManager: zero address"); + require(addr != address(0), "AllowListOperatorManager: zero address"); _; } constructor( address[] memory _operators, - bool[] memory _allowed, address[] memory _feeReceivers, - bool _whitelistEnabled + bool _allowListEnabled ) Ownable() { - allowListEnabled = _whitelistEnabled; + allowListEnabled = _allowListEnabled; + bool[] memory _allowed = new bool[](_operators.length); + for (uint256 i = 0; i < _operators.length; i++) { + _allowed[i] = true; + } _setOperators(_operators, _allowed, _feeReceivers); } @@ -69,8 +72,8 @@ contract AllowListOperatorManager is IOperatorManager, Ownable { bool[] memory _allowed, address[] memory _feeReceivers ) internal { - require(_operators.length == _feeReceivers.length, "WhitelistBasedOperatorManager: arrays length mismatch"); - require(_operators.length == _allowed.length, "WhitelistBasedOperatorManager: arrays length mismatch"); + require(_operators.length == _feeReceivers.length, "AllowListOperatorManager: arrays length mismatch"); + require(_operators.length == _allowed.length, "AllowListOperatorManager: arrays length mismatch"); for (uint256 i = 0; i < _operators.length; i++) { _setOperator(_operators[i], _allowed[i], _feeReceivers[i]); diff --git a/test/interfaces/IZkBobPoolAdmin.sol b/test/interfaces/IZkBobPoolAdmin.sol index ff35cc4..c71a906 100644 --- a/test/interfaces/IZkBobPoolAdmin.sol +++ b/test/interfaces/IZkBobPoolAdmin.sol @@ -11,7 +11,7 @@ interface IZkBobPoolAdmin { function pool_index() external view returns (uint256); - function pendingCommitment() external view returns (uint256); + function pendingCommitment() external view returns (uint256, address, uint64, uint64); function initialize(uint256 _root) external; diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 171a008..18b0f77 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -282,7 +282,8 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { function _proveTreeUpdate() internal { vm.startPrank(user2); - pool.proveTreeUpdate(pool.pendingCommitment(), _randProof(), _randFR()); + (uint256 commitment, , , ) = pool.pendingCommitment(); + pool.proveTreeUpdate(commitment, _randProof(), _randFR()); vm.stopPrank(); } diff --git a/test/zkbob/ZkBobPoolDecentralized.t.sol b/test/zkbob/ZkBobPoolDecentralized.t.sol index 80f2744..f4af4a8 100644 --- a/test/zkbob/ZkBobPoolDecentralized.t.sol +++ b/test/zkbob/ZkBobPoolDecentralized.t.sol @@ -22,12 +22,11 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas address[] provers = [prover1, prover2]; address[] feeReceivers = [feeReceiver1, feeReceiver2]; - bool[] allowed = [true, true]; function setUp() public override { super.setUp(); - manager = new AllowListOperatorManager(provers, allowed, feeReceivers, true); + manager = new AllowListOperatorManager(provers, feeReceivers, true); pool.setOperatorManager(IOperatorManager(manager)); } @@ -179,6 +178,64 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas _proveTreeUpdate(prover2); } + function testDirectDepositsTreeUpdateFeeTooLow() public { + _setUpDD(); + + pool.setMinTreeUpdateFee(uint64(3)); + queue.setDirectDepositFee(uint64(1)); + + vm.startPrank(user1); + + _directDeposit(10 ether / D, user2, zkAddress); + _directDeposit(5 ether / D, user2, zkAddress); + vm.stopPrank(); + + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 1; + + uint256 outCommitment = _randFR(); + vm.prank(prover1); + vm.expectRevert("ZkBobPool: tree update fee is too low"); + pool.appendDirectDeposits(indices, outCommitment, _randProof()); + } + + function testDirectDepositsTreeFeesAccrued() public { + _setUpDD(); + + uint64 minTreeUpdateFee = uint64(0.01 ether / (D * denominator)); + uint64 singleDirectDepositFee = uint64(0.1 ether / (D * denominator)); + + pool.setMinTreeUpdateFee(minTreeUpdateFee); + queue.setDirectDepositFee(singleDirectDepositFee); + + vm.startPrank(user1); + _directDeposit(10 ether / D, user2, zkAddress); + _directDeposit(5 ether / D, user2, zkAddress); + vm.stopPrank(); + + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 1; + + uint256 outCommitment = _randFR(); + vm.prank(prover1); + pool.appendDirectDeposits(indices, outCommitment, _randProof()); + uint64 expectedFee = uint64(singleDirectDepositFee* 2 - minTreeUpdateFee); + assertEq(expectedFee, pool.accumulatedFee(prover1)); + + vm.prank(prover1); + pool.withdrawFee(prover1, feeReceiver1); + assertEq(IERC20(token).balanceOf(feeReceiver1), expectedFee * denominator); + + _proveTreeUpdate(prover1); + assertEq(minTreeUpdateFee, pool.accumulatedFee(prover1)); + + vm.prank(prover1); + pool.withdrawFee(prover1, feeReceiver1); + assertEq(IERC20(token).balanceOf(feeReceiver1), (expectedFee + minTreeUpdateFee) * denominator); + } + function _transact(bytes memory _data, address caller) internal { vm.prank(caller); (bool status,) = address(pool).call(_data); @@ -197,13 +254,13 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas } function _proveTreeUpdate(address caller) internal { - uint256 commitment = pool.pendingCommitment(); + (uint256 commitment, , , ) = pool.pendingCommitment(); vm.prank(caller); pool.proveTreeUpdate(commitment, _randProof(), _randFR()); } function _proveTreeUpdateExpectRevert(address caller, string memory expectedRevertReason) internal { - uint256 commitment = pool.pendingCommitment(); + (uint256 commitment, , , ) = pool.pendingCommitment(); vm.expectRevert(bytes(expectedRevertReason)); vm.prank(caller); pool.proveTreeUpdate(commitment, _randProof(), _randFR()); @@ -238,53 +295,6 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas vm.prank(prover); pool.appendDirectDeposits(indices, commitment, proof); } - - function testDirectDepositsTreeUpdateFeeTooLow() public { - _setUpDD(); - - pool.setMinTreeUpdateFee(uint64(3)); - queue.setDirectDepositFee(uint64(1)); - - vm.startPrank(user1); - - _directDeposit(10 ether / D, user2, zkAddress); - _directDeposit(5 ether / D, user2, zkAddress); - vm.stopPrank(); - - uint256[] memory indices = new uint256[](2); - indices[0] = 0; - indices[1] = 1; - address verifier = address(pool.batch_deposit_verifier()); - uint256 outCommitment = _randFR(); - vm.prank(prover1); - vm.expectRevert("ZkBobPool: tree update fee is too low"); - pool.appendDirectDeposits(indices, outCommitment, _randProof()); - } - - function testDirectDepositsTreeFeesAccrued() public { - _setUpDD(); - - uint64 minTreeUpdateFee = uint64(0.01 ether / (D * denominator)); - uint64 singleDirectDepositFee = uint64(0.1 ether / (D * denominator)); - - pool.setMinTreeUpdateFee(minTreeUpdateFee); - queue.setDirectDepositFee(singleDirectDepositFee); - - vm.startPrank(user1); - _directDeposit(10 ether / D, user2, zkAddress); - _directDeposit(5 ether / D, user2, zkAddress); - vm.stopPrank(); - - uint256[] memory indices = new uint256[](2); - indices[0] = 0; - indices[1] = 1; - address verifier = address(pool.batch_deposit_verifier()); - uint256 outCommitment = _randFR(); - vm.prank(prover1); - pool.appendDirectDeposits(indices, outCommitment, _randProof()); - uint64 expectedFee = uint64(singleDirectDepositFee* 2 - minTreeUpdateFee); - assertEq(expectedFee, pool.accumulatedFee(prover1)); - } } contract ZkBobPoolBOBPolygonDecentralizedTest is AbstractZkBobPoolDecentralizedTest, AbstractPolygonForkTest { diff --git a/test/zkbob/manager/AllowListOperatorManager.t.sol b/test/zkbob/manager/AllowListOperatorManager.t.sol new file mode 100644 index 0000000..7aae481 --- /dev/null +++ b/test/zkbob/manager/AllowListOperatorManager.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.15; + +import {Test} from "forge-std/Test.sol"; +import {AllowListOperatorManager} from "../../../src/zkbob/manager/AllowListOperatorManager.sol"; +import "../../shared/Env.t.sol"; + +contract AllowListOperatorManagerTest is Test { + address operator1 = makeAddr("operator1"); + address feeReceiver1 = makeAddr("feeReceiver1"); + + address operator2 = makeAddr("operator2"); + address feeReceiver2 = makeAddr("feeReceiver2"); + + address unauthorized = makeAddr("unauthorized"); + address unauthorizedFeeReceiver = makeAddr("unauthorizedFeeReceiver"); + + AllowListOperatorManager manager; + + function testConstructor() public { + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + + address[] memory feeReceivers = new address[](1); + feeReceivers[0] = feeReceiver1; + + vm.expectRevert("AllowListOperatorManager: arrays length mismatch"); + manager = new AllowListOperatorManager(operators, feeReceivers, true); + + feeReceivers = new address[](2); + feeReceivers[0] = feeReceiver1; + feeReceivers[1] = feeReceiver2; + operators[0] = address(0); + + vm.expectRevert("AllowListOperatorManager: zero address"); + manager = new AllowListOperatorManager(operators, feeReceivers, true); + + operators[0] = operator1; + manager = new AllowListOperatorManager(operators, feeReceivers, true); + assertEq(manager.owner(), address(this)); + + assertTrue(manager.isOperator(operator1)); + assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver1)); + + assertTrue(manager.isOperator(operator2)); + assertTrue(manager.isOperatorFeeReceiver(operator2, feeReceiver2)); + + assertFalse(manager.isOperator(unauthorized)); + assertFalse(manager.isOperator(address(this))); + + // TODO: + manager = new AllowListOperatorManager(operators, feeReceivers, false); + assertEq(manager.owner(), address(this)); + + assertTrue(manager.isOperator(operator1)); + assertTrue(manager.isOperator(operator2)); + assertTrue(manager.isOperator(unauthorized)); + assertTrue(manager.isOperator(makeAddr("random"))); + } +} \ No newline at end of file From eeb3821801499794fdd283149464274a7bead6f7 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Tue, 12 Dec 2023 16:17:41 +0300 Subject: [PATCH 08/40] Add AllowListOperatorManager tests --- .../manager/AllowListOperatorManager.sol | 96 ++++++---- .../manager/AllowListOperatorManager.t.sol | 171 +++++++++++++++++- 2 files changed, 227 insertions(+), 40 deletions(-) diff --git a/src/zkbob/manager/AllowListOperatorManager.sol b/src/zkbob/manager/AllowListOperatorManager.sol index 7164e21..2a8e271 100644 --- a/src/zkbob/manager/AllowListOperatorManager.sol +++ b/src/zkbob/manager/AllowListOperatorManager.sol @@ -2,38 +2,33 @@ pragma solidity 0.8.15; -import "../../interfaces/IOperatorManager.sol"; -import "../../utils/Ownable.sol"; +import {IOperatorManager} from "../../interfaces/IOperatorManager.sol"; +import {Ownable} from "../../utils/Ownable.sol"; /** * @title AllowListOperatorManager - * @dev Implements a allow list based access control for ZkBobPool relayers. + * @dev Implements an allow list based access control for ZkBobPool relayers. */ contract AllowListOperatorManager is IOperatorManager, Ownable { // if true, only whitelisted addresses can be operators // if false, anyone can be an operator bool public allowListEnabled; - + // mapping of whitelisted operator addresses mapping(address => bool) public operators; - // mapping of all historical fee receiver addresses, we keep fee receivers addresses - // in a mapping to allow fee withdrawals even after operator address was changed + // mapping of fee receivers for operators mapping(address => address) public operatorFeeReceiver; event UpdateOperator(address indexed operator, address feeReceiver, bool allowed); event UpdateAllowListEnabled(bool enabled); modifier nonZeroAddress(address addr) { - require(addr != address(0), "AllowListOperatorManager: zero address"); + require(addr != address(0), "OperatorManager: zero address"); _; } - constructor( - address[] memory _operators, - address[] memory _feeReceivers, - bool _allowListEnabled - ) Ownable() { + constructor(address[] memory _operators, address[] memory _feeReceivers, bool _allowListEnabled) Ownable() { allowListEnabled = _allowListEnabled; bool[] memory _allowed = new bool[](_operators.length); for (uint256 i = 0; i < _operators.length; i++) { @@ -41,61 +36,96 @@ contract AllowListOperatorManager is IOperatorManager, Ownable { } _setOperators(_operators, _allowed, _feeReceivers); } - + + /** + * @dev Doesn't return any data, as operator URI is not used in this implementation. + */ function operatorURI() external pure returns (string memory) { return ""; } + /** + * @dev Sets the allow list enabled flag. + * @param _allowListEnabled flag to enable or disable allow list. + */ function setAllowListEnabled(bool _allowListEnabled) external onlyOwner { allowListEnabled = _allowListEnabled; emit UpdateAllowListEnabled(_allowListEnabled); } + /** + * @dev Adds or removes an operator from the allow list. + * @param _operator address of the operator. + * @param _allowed flag to enable or disable operator. + * @param _feeReceiver address of the fee receiver. + */ + function setOperator(address _operator, address _feeReceiver, bool _allowed) external onlyOwner { + _setOperator(_operator, _allowed, _feeReceiver); + } + + /** + * @dev Adds or removes operators from the allow list. + * @param _operators addresses of the operators. + * @param _allowed flags to enable or disable operators. + * @param _feeReceivers addresses of the fee receivers. + */ function setOperators( - address[] calldata _operators, + address[] calldata _operators, bool[] calldata _allowed, address[] calldata _feeReceivers - ) external onlyOwner { + ) + external + onlyOwner + { _setOperators(_operators, _allowed, _feeReceivers); } - function setOperator( - address _operator, - address _feeReceiver, - bool _allowed - ) external onlyOwner { - _setOperator(_operator, _allowed, _feeReceiver); + /** + * @dev Sets the fee receiver for the operator. + * @param _feeReceiver address of the fee receiver. + */ + function setFeeReceiver(address _feeReceiver) external { + require(isOperator(msg.sender), "OperatorManager: operator not allowed"); + operatorFeeReceiver[msg.sender] = _feeReceiver; + emit UpdateOperator(msg.sender, _feeReceiver, true); } function _setOperators( - address[] memory _operators, + address[] memory _operators, bool[] memory _allowed, address[] memory _feeReceivers - ) internal { - require(_operators.length == _feeReceivers.length, "AllowListOperatorManager: arrays length mismatch"); - require(_operators.length == _allowed.length, "AllowListOperatorManager: arrays length mismatch"); - + ) + internal + { + require(_operators.length == _feeReceivers.length, "OperatorManager: arrays length mismatch"); + require(_operators.length == _allowed.length, "OperatorManager: arrays length mismatch"); + for (uint256 i = 0; i < _operators.length; i++) { _setOperator(_operators[i], _allowed[i], _feeReceivers[i]); } } - function _setOperator( - address _operator, - bool _allowed, - address _feeReceiver - ) nonZeroAddress(_operator) internal { + function _setOperator(address _operator, bool _allowed, address _feeReceiver) internal nonZeroAddress(_operator) { operators[_operator] = _allowed; if (_feeReceiver != address(0) && _allowed) { operatorFeeReceiver[_operator] = _feeReceiver; } - emit UpdateOperator(_operator, _feeReceiver, _allowed); + emit UpdateOperator(_operator, operatorFeeReceiver[_operator], _allowed); } - function isOperator(address _addr) external view override returns (bool) { + /** + * @dev Returns true if the address is an operator. + * @param _addr address to check. + */ + function isOperator(address _addr) public view override returns (bool) { return operators[_addr] || !allowListEnabled; } + /** + * @dev Returns true if the address is an operator fee receiver. + * @param _operator address of the operator. + * @param _addr address to check. + */ function isOperatorFeeReceiver(address _operator, address _addr) external view override returns (bool) { return operatorFeeReceiver[_operator] == _addr; } diff --git a/test/zkbob/manager/AllowListOperatorManager.t.sol b/test/zkbob/manager/AllowListOperatorManager.t.sol index 7aae481..67e2d24 100644 --- a/test/zkbob/manager/AllowListOperatorManager.t.sol +++ b/test/zkbob/manager/AllowListOperatorManager.t.sol @@ -26,7 +26,7 @@ contract AllowListOperatorManagerTest is Test { address[] memory feeReceivers = new address[](1); feeReceivers[0] = feeReceiver1; - vm.expectRevert("AllowListOperatorManager: arrays length mismatch"); + vm.expectRevert("OperatorManager: arrays length mismatch"); manager = new AllowListOperatorManager(operators, feeReceivers, true); feeReceivers = new address[](2); @@ -34,29 +34,186 @@ contract AllowListOperatorManagerTest is Test { feeReceivers[1] = feeReceiver2; operators[0] = address(0); - vm.expectRevert("AllowListOperatorManager: zero address"); + vm.expectRevert("OperatorManager: zero address"); manager = new AllowListOperatorManager(operators, feeReceivers, true); operators[0] = operator1; manager = new AllowListOperatorManager(operators, feeReceivers, true); assertEq(manager.owner(), address(this)); + assertTrue(manager.allowListEnabled()); assertTrue(manager.isOperator(operator1)); assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver1)); - + assertTrue(manager.isOperator(operator2)); assertTrue(manager.isOperatorFeeReceiver(operator2, feeReceiver2)); - + assertFalse(manager.isOperator(unauthorized)); assertFalse(manager.isOperator(address(this))); - // TODO: manager = new AllowListOperatorManager(operators, feeReceivers, false); assertEq(manager.owner(), address(this)); - + assertFalse(manager.allowListEnabled()); + assertTrue(manager.isOperator(operator1)); + assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver1)); assertTrue(manager.isOperator(operator2)); + assertTrue(manager.isOperatorFeeReceiver(operator2, feeReceiver2)); + assertTrue(manager.isOperator(unauthorized)); assertTrue(manager.isOperator(makeAddr("random"))); } -} \ No newline at end of file + + function testOperatorURI() public { + manager = new AllowListOperatorManager(new address[](0), new address[](0), true); + assertEq(manager.operatorURI(), ""); + } + + function testSetAllowListEnabled() public { + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + + address[] memory feeReceivers = new address[](2); + feeReceivers[0] = feeReceiver1; + feeReceivers[1] = feeReceiver2; + + manager = new AllowListOperatorManager(operators, feeReceivers, true); + assertTrue(manager.allowListEnabled()); + assertTrue(manager.isOperator(operator1)); + assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver1)); + assertTrue(manager.isOperator(operator2)); + assertTrue(manager.isOperatorFeeReceiver(operator2, feeReceiver2)); + assertFalse(manager.isOperator(unauthorized)); + + vm.prank(makeAddr("not owner")); + vm.expectRevert("Ownable: caller is not the owner"); + manager.setAllowListEnabled(false); + + manager.setAllowListEnabled(false); + assertFalse(manager.allowListEnabled()); + assertTrue(manager.isOperator(unauthorized)); + + manager.setAllowListEnabled(true); + assertTrue(manager.allowListEnabled()); + assertTrue(manager.isOperator(operator1)); + assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver1)); + assertTrue(manager.isOperator(operator2)); + assertTrue(manager.isOperatorFeeReceiver(operator2, feeReceiver2)); + assertFalse(manager.isOperator(unauthorized)); + } + + function testSetOperator() public { + address[] memory operators = new address[](1); + operators[0] = operator1; + + address[] memory feeReceivers = new address[](1); + feeReceivers[0] = feeReceiver1; + + manager = new AllowListOperatorManager(operators, feeReceivers, true); + assertTrue(manager.isOperator(operator1)); + assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver1)); + assertFalse(manager.isOperator(operator2)); + assertFalse(manager.isOperatorFeeReceiver(operator2, feeReceiver2)); + + vm.prank(makeAddr("not owner")); + vm.expectRevert("Ownable: caller is not the owner"); + manager.setOperator(operator2, feeReceiver2, true); + + manager.setOperator(operator2, feeReceiver2, true); + assertTrue(manager.isOperator(operator2)); + assertTrue(manager.isOperatorFeeReceiver(operator2, feeReceiver2)); + + manager.setOperator(operator1, feeReceiver1, false); + assertFalse(manager.isOperator(operator1)); + // Even if operator was removed, we still allow to claim accumulated fees + assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver1)); + + manager.setOperator(operator1, feeReceiver1, true); + assertTrue(manager.isOperator(operator1)); + assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver1)); + assertTrue(manager.isOperator(operator2)); + assertTrue(manager.isOperatorFeeReceiver(operator2, feeReceiver2)); + assertFalse(manager.isOperator(unauthorized)); + } + + function testSetOperators() public { + address[] memory operators = new address[](1); + operators[0] = operator1; + + address[] memory feeReceivers = new address[](1); + feeReceivers[0] = feeReceiver1; + + manager = new AllowListOperatorManager(operators, feeReceivers, true); + assertTrue(manager.isOperator(operator1)); + assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver1)); + + operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + + feeReceivers = new address[](1); + feeReceivers[0] = feeReceiver1; + + bool[] memory allowed = new bool[](2); + allowed[0] = true; + allowed[1] = true; + + vm.expectRevert("OperatorManager: arrays length mismatch"); + manager.setOperators(operators, allowed, feeReceivers); + + feeReceivers = new address[](2); + feeReceivers[0] = feeReceiver1; + feeReceivers[1] = feeReceiver2; + + allowed = new bool[](1); + allowed[0] = true; + + vm.expectRevert("OperatorManager: arrays length mismatch"); + manager.setOperators(operators, allowed, feeReceivers); + + operators = new address[](10); + operators[0] = operator1; + feeReceivers = new address[](10); + feeReceivers[0] = address(0); + allowed = new bool[](10); + for (uint256 i = 1; i < 10; i++) { + operators[i] = address(uint160(i)); + feeReceivers[i] = address(uint160(2 * i + 1)); + allowed[i] = i % 2 != 0; + } + + vm.prank(makeAddr("not owner")); + vm.expectRevert("Ownable: caller is not the owner"); + manager.setOperators(operators, allowed, feeReceivers); + + manager.setOperators(operators, allowed, feeReceivers); + for (uint256 i = 0; i < 10; i++) { + assertEq(manager.isOperator(operators[i]), allowed[i]); + if (i > 0) { + assertEq(manager.isOperatorFeeReceiver(operators[i], feeReceivers[i]), allowed[i]); + } + } + assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver1)); + } + + function testSetFeeReceiver() public { + address[] memory operators = new address[](1); + operators[0] = operator1; + + address[] memory feeReceivers = new address[](1); + feeReceivers[0] = feeReceiver1; + + manager = new AllowListOperatorManager(operators, feeReceivers, true); + assertTrue(manager.isOperator(operator1)); + assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver1)); + + vm.prank(operator1); + manager.setFeeReceiver(feeReceiver2); + assertTrue(manager.isOperatorFeeReceiver(operator1, feeReceiver2)); + + vm.prank(unauthorized); + vm.expectRevert("OperatorManager: operator not allowed"); + manager.setFeeReceiver(unauthorizedFeeReceiver); + } +} From 91053a280f70fab430efec7b44ec992611ac929b Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Wed, 13 Dec 2023 12:25:32 +0300 Subject: [PATCH 09/40] Minor refactoring --- src/zkbob/ZkBobPool.sol | 21 +++++++++++++------- src/zkbob/utils/PriorityQueue.sol | 31 +++++++++++++++--------------- test/libraries/PriorityQueue.t.sol | 15 ++++++++------- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index f0a1204..a95f9d5 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -24,7 +24,7 @@ import "../utils/Ownable.sol"; import "../proxy/EIP1967Admin.sol"; import "../interfaces/IEnergyRedeemer.sol"; import "../utils/ExternalSload.sol"; -import {PriorityQueue, PriorityOperation} from "./utils/PriorityQueue.sol"; +import {PriorityQueue, PendingCommitment} from "./utils/PriorityQueue.sol"; /** * @title ZkBobPool @@ -168,15 +168,22 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex return TOKEN_NUMERATOR == 1 ? TOKEN_DENOMINATOR : (1 << 255) | TOKEN_NUMERATOR; } + /** + * @dev Returns the first pending commitment in the prioirty queue. + * @return commitment commitment value. + * @return privilegedProver prover that can submit the tree update proof within the grace period. + * @return fee fee reserved for the prover who will submit the tree update proof. + * @return timestamp commitment timestamp. + */ function pendingCommitment() external view returns ( uint256 commitment, address privilegedProver, - uint64 timestamp, - uint64 fee + uint64 fee, + uint64 timestamp ) { - PriorityOperation memory op = pendingCommitments.front(); + PendingCommitment memory op = pendingCommitments.front(); require(op.commitment != 0, "ZkBobPool: no pending commitment"); - return (op.commitment, op.prover, op.timestamp, op.fee); + return (op.commitment, op.prover, op.fee, op.timestamp); } /** @@ -416,7 +423,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex uint256[8] calldata _proof, uint256 _rootAfter ) external onlyOperator { - PriorityOperation memory commitment = pendingCommitments.popFront(); + PendingCommitment memory commitment = pendingCommitments.popFront(); require(commitment.commitment == _commitment, "ZkBobPool: commitment mismatch"); _validateGracePeriod(commitment.timestamp, commitment.prover); @@ -610,7 +617,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex * @dev Appends a commitment to the pending commitments queue. */ function _appendCommitment(uint256 _commitment, uint64 _fee, address _prover) internal { - pendingCommitments.pushBack(PriorityOperation({ + pendingCommitments.pushBack(PendingCommitment({ commitment: _commitment, fee: _fee, prover: _prover, diff --git a/src/zkbob/utils/PriorityQueue.sol b/src/zkbob/utils/PriorityQueue.sol index 1935731..a96b02d 100644 --- a/src/zkbob/utils/PriorityQueue.sol +++ b/src/zkbob/utils/PriorityQueue.sol @@ -2,12 +2,14 @@ pragma solidity ^0.8.13; -/// @notice The structure that contains meta information of the L2 transaction that was requested from L1 -/// @dev The weird size of fields was selected specifically to minimize the structure storage size -/// @param canonicalTxHash Hashed L2 transaction data that is needed to process it -/// @param expirationTimestamp Expiration timestamp for this request (must be satisfied before) -/// @param layer2Tip Additional payment to the validator as an incentive to perform the operation -struct PriorityOperation { +/** + * @dev The structure that stores all information about the pending commitment. + * @param commitment commitment value to be added in the Merkle Tree. + * @param prover address of the prover that submitted the commitment. + * @param fee fee reserved for the prover who will submit the tree update proof. + * @param timestamp commitment timestamp. + */ +struct PendingCommitment { uint256 commitment; address prover; uint64 fee; @@ -15,7 +17,6 @@ struct PriorityOperation { } /// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev /// @dev The library provides the API to interact with the priority queue container /// @dev Order of processing operations from queue - FIFO (Fist in - first out) library PriorityQueue { @@ -26,7 +27,7 @@ library PriorityQueue { /// @param head The pointer to the first unprocessed priority operation, equal to the tail if the queue is empty /// @param tail The pointer to the free slot struct Queue { - mapping(uint256 => PriorityOperation) data; + mapping(uint256 => PendingCommitment) data; uint256 tail; uint256 head; } @@ -53,7 +54,7 @@ library PriorityQueue { } /// @notice Add the priority operation to the end of the priority queue - function pushBack(Queue storage _queue, PriorityOperation memory _operation) internal { + function pushBack(Queue storage _queue, PendingCommitment memory _operation) internal { // Save value into the stack to avoid double reading from the storage uint256 tail = _queue.tail; @@ -61,8 +62,8 @@ library PriorityQueue { _queue.tail = tail + 1; } - function list(Queue storage _queue) external view returns ( PriorityOperation[] memory) { - PriorityOperation[] memory result = new PriorityOperation[] (_queue.getSize()); + function list(Queue storage _queue) external view returns ( PendingCommitment[] memory) { + PendingCommitment[] memory result = new PendingCommitment[] (_queue.getSize()); for (uint256 index = _queue.head; index < _queue.tail; index++) { result[index-_queue.head] = _queue.data[index]; } @@ -70,21 +71,21 @@ library PriorityQueue { } /// @return The first unprocessed priority operation from the queue - function front(Queue storage _queue) internal view returns (PriorityOperation memory) { + function front(Queue storage _queue) internal view returns (PendingCommitment memory) { require(!_queue.isEmpty(), "D"); // priority queue is empty return _queue.data[_queue.head]; } /// @notice Remove the first unprocessed priority operation from the queue - /// @return priorityOperation that was popped from the priority queue - function popFront(Queue storage _queue) internal returns (PriorityOperation memory priorityOperation) { + /// @return pendingCommitment that was popped from the priority queue + function popFront(Queue storage _queue) internal returns (PendingCommitment memory pendingCommitment) { require(!_queue.isEmpty(), "s"); // priority queue is empty // Save value into the stack to avoid double reading from the storage uint256 head = _queue.head; - priorityOperation = _queue.data[head]; + pendingCommitment = _queue.data[head]; delete _queue.data[head]; _queue.head = head + 1; } diff --git a/test/libraries/PriorityQueue.t.sol b/test/libraries/PriorityQueue.t.sol index 6f3076c..09c1579 100644 --- a/test/libraries/PriorityQueue.t.sol +++ b/test/libraries/PriorityQueue.t.sol @@ -9,11 +9,11 @@ contract DummyQueue { PriorityQueue.Queue _queue; address immutable prover1 = address(bytes20(new bytes(20))); - function list() external view returns (PriorityOperation[] memory) { + function list() external view returns (PendingCommitment[] memory) { return _queue.list(); } - function pushBack(PriorityOperation memory _operation) external { + function pushBack(PendingCommitment memory _operation) external { _queue.pushBack(_operation); } @@ -25,7 +25,7 @@ contract DummyQueue { return _queue.tail; } - function popFront() external returns (PriorityOperation memory priorityOperation) { + function popFront() external returns (PendingCommitment memory pendingCommitments) { return _queue.popFront(); } } @@ -42,12 +42,12 @@ contract PriorityQueueTest is Test { function newOp( uint256 id - ) external view returns (PriorityOperation memory) { - return PriorityOperation(id, prover1, uint64(0), uint64(0)); + ) external view returns (PendingCommitment memory) { + return PendingCommitment(id, prover1, uint64(0), uint64(0)); } function testEmptyQueue() external { - PriorityOperation[] memory ops = _queueContract.list(); + PendingCommitment[] memory ops = _queueContract.list(); assertEq(0, ops.length); } @@ -63,6 +63,7 @@ contract PriorityQueueTest is Test { assertEq(2, _queueContract.list().length); } + function testPopFront() external { _queueContract.pushBack(this.newOp(0)); _queueContract.pushBack(this.newOp(1)); @@ -71,7 +72,7 @@ contract PriorityQueueTest is Test { assertEq(0, _queueContract.head()); assertEq(3, _queueContract.tail()); - PriorityOperation memory first = _queueContract.popFront(); + PendingCommitment memory first = _queueContract.popFront(); assertEq(first.commitment, uint256(0)); From 71685539333efaa95869860bcc50d87afed70788 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Wed, 13 Dec 2023 13:37:25 +0300 Subject: [PATCH 10/40] Minor fixes --- src/zkbob/ZkBobPool.sol | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index a95f9d5..0c34be4 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -63,21 +63,28 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex mapping(address => uint256) public accumulatedFee; + /** + * @dev Queue of pending commitments to be included in the Merkle Tree. + */ PriorityQueue.Queue internal pendingCommitments; + + /** + * @dev Timestamp of the last tree update. + */ uint64 internal lastTreeUpdateTimestamp; /** * @dev The duration of the grace period within which only the prover who submitted the transaction * can submit the tree update proof. */ - uint64 public gracePeriod = 5 minutes; + uint64 public gracePeriod; /** * @dev The minimal fee required to be reserved for the prover who will submit the tree update proof. * This fee is used to prevent spamming the pool with transactions that don't incentivize provers * to provide tree update proof. */ - uint64 public minTreeUpdateFee = 0; + uint64 public minTreeUpdateFee; event UpdateOperatorManager(address manager); event UpdateAccounting(address accounting); @@ -129,6 +136,14 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex _; } + /** + * @dev Throws if minimal tree update fee is not set. + */ + modifier minTreeUpdateFeeIsSet() { + require(minTreeUpdateFee > 0, "ZkBobPool: minimal tree update fee is not set"); + _; + } + /** * @dev Initializes pool proxy storage. * Callable only once and only through EIP1967Proxy constructor / upgradeToAndCall. @@ -261,7 +276,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex * Method uses a custom ABI encoding scheme described in CustomABIDecoder. * Single transact() call performs either deposit, withdrawal or shielded transfer operation. */ - function transact() external onlyOperator { + function transact() external onlyOperator minTreeUpdateFeeIsSet { address user = msg.sender; uint256 txType = _tx_type(); if (txType == 0) { @@ -311,7 +326,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex uint256 transactFee = _memo_transact_fee(); uint256 treeUpdateFee = _memo_tree_update_fee(); - require(minTreeUpdateFee > 0, "ZkBobPool: minimal tree update fee is not set"); require(treeUpdateFee >= minTreeUpdateFee, "ZkBobPool: tree update fee is too low"); uint256 fee = transactFee + treeUpdateFee; @@ -375,6 +389,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex ) external onlyOperator + minTreeUpdateFeeIsSet { (uint256 total, uint256 totalFee, uint256 hashsum, bytes memory message) = direct_deposit_queue.collect(_indices, _out_commit); @@ -390,7 +405,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex ); // we reserve the minimal tree update fee for the prover who will submit the tree update proof - require(minTreeUpdateFee > 0, "ZkBobPool: minimal tree update fee is not set"); require(totalFee >= minTreeUpdateFee, "ZkBobPool: tree update fee is too low"); uint64 ddFee = uint64(totalFee) - minTreeUpdateFee; From f823aec57b3bcc0989f11baa0fca8a301c8419ad Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Wed, 13 Dec 2023 15:28:32 +0300 Subject: [PATCH 11/40] Refactor PriorityQueue --- src/zkbob/utils/PriorityQueue.sol | 37 +++----- test/libraries/PriorityQueue.t.sol | 135 ++++++++++++++++++----------- 2 files changed, 95 insertions(+), 77 deletions(-) diff --git a/src/zkbob/utils/PriorityQueue.sol b/src/zkbob/utils/PriorityQueue.sol index a96b02d..eb3f0d1 100644 --- a/src/zkbob/utils/PriorityQueue.sol +++ b/src/zkbob/utils/PriorityQueue.sol @@ -22,9 +22,9 @@ struct PendingCommitment { library PriorityQueue { using PriorityQueue for Queue; - /// @notice Container that stores priority operations - /// @param data The inner mapping that saves priority operation by its index - /// @param head The pointer to the first unprocessed priority operation, equal to the tail if the queue is empty + /// @notice Container that stores pending commitments + /// @param data The inner mapping that saves poending commitment by its index + /// @param head The pointer to the first unprocessed pending commitment, equal to the tail if the queue is empty /// @param tail The pointer to the free slot struct Queue { mapping(uint256 => PendingCommitment) data; @@ -32,37 +32,26 @@ library PriorityQueue { uint256 head; } - /// @notice Returns zero if and only if no operations were processed from the queue - /// @return Index of the oldest priority operation that wasn't processed yet - function getFirstUnprocessedPriorityTx(Queue storage _queue) internal view returns (uint256) { - return _queue.head; - } - - /// @return The total number of priority operations that were added to the priority queue, including all processed ones - function getTotalPriorityTxs(Queue storage _queue) internal view returns (uint256) { - return _queue.tail; - } - - /// @return The total number of unprocessed priority operations in a priority queue + /// @return The total number of unprocessed pending commitments in a priority queue function getSize(Queue storage _queue) internal view returns (uint256) { return uint256(_queue.tail - _queue.head); } - /// @return Whether the priority queue contains no operations + /// @return Whether the priority queue contains no pending commitments function isEmpty(Queue storage _queue) internal view returns (bool) { return _queue.tail == _queue.head; } - /// @notice Add the priority operation to the end of the priority queue - function pushBack(Queue storage _queue, PendingCommitment memory _operation) internal { + /// @notice Add the pending commitment to the end of the priority queue + function pushBack(Queue storage _queue, PendingCommitment memory _commitment) internal { // Save value into the stack to avoid double reading from the storage uint256 tail = _queue.tail; - _queue.data[tail] = _operation; + _queue.data[tail] = _commitment; _queue.tail = tail + 1; } - function list(Queue storage _queue) external view returns ( PendingCommitment[] memory) { + function list(Queue storage _queue) internal view returns ( PendingCommitment[] memory) { PendingCommitment[] memory result = new PendingCommitment[] (_queue.getSize()); for (uint256 index = _queue.head; index < _queue.tail; index++) { result[index-_queue.head] = _queue.data[index]; @@ -70,17 +59,17 @@ library PriorityQueue { return result; } - /// @return The first unprocessed priority operation from the queue + /// @return The first unprocessed pending commitment from the queue function front(Queue storage _queue) internal view returns (PendingCommitment memory) { - require(!_queue.isEmpty(), "D"); // priority queue is empty + require(!_queue.isEmpty(), "ZkBobPool: queue is empty"); // priority queue is empty return _queue.data[_queue.head]; } - /// @notice Remove the first unprocessed priority operation from the queue + /// @notice Remove the first unprocessed pending commitment from the queue /// @return pendingCommitment that was popped from the priority queue function popFront(Queue storage _queue) internal returns (PendingCommitment memory pendingCommitment) { - require(!_queue.isEmpty(), "s"); // priority queue is empty + require(!_queue.isEmpty(), "ZkBobPool: queue is empty"); // priority queue is empty // Save value into the stack to avoid double reading from the storage uint256 head = _queue.head; diff --git a/test/libraries/PriorityQueue.t.sol b/test/libraries/PriorityQueue.t.sol index 09c1579..dc32da4 100644 --- a/test/libraries/PriorityQueue.t.sol +++ b/test/libraries/PriorityQueue.t.sol @@ -1,84 +1,113 @@ +// SPDX-License-Identifier: CC0-1.0 + pragma solidity ^0.8.15; + import "forge-std/Test.sol"; -import "../../src/zkbob/utils/PriorityQueue.sol"; +import {PriorityQueue, PendingCommitment} from "../../src/zkbob/utils/PriorityQueue.sol"; import "forge-std/console.sol"; -contract DummyQueue { - using PriorityQueue for PriorityQueue.Queue; - - PriorityQueue.Queue _queue; - address immutable prover1 = address(bytes20(new bytes(20))); +contract PriorityQueueTest is Test { + address immutable prover1 = makeAddr("Prover #1"); + DummyQueue _queue; - function list() external view returns (PendingCommitment[] memory) { - return _queue.list(); + function setUp() external { + _queue = new DummyQueue(); } - function pushBack(PendingCommitment memory _operation) external { - _queue.pushBack(_operation); - } + function testEmptyQueue() external { + assertEq(_queue.getSize(), 0); + assertEq(_queue.isEmpty(), true); - function head() external view returns (uint256) { - return _queue.head; - } + vm.expectRevert("ZkBobPool: queue is empty"); + _queue.popFront(); - function tail() external view returns (uint256) { - return _queue.tail; - } + vm.expectRevert("ZkBobPool: queue is empty"); + _queue.front(); - function popFront() external returns (PendingCommitment memory pendingCommitments) { - return _queue.popFront(); + PendingCommitment[] memory ops = _queue.list(); + assertEq(0, ops.length); } -} -contract PriorityQueueTest is Test { - using PriorityQueue for PriorityQueue.Queue; + function testPushBackPopFront() external { + for (uint256 i = 0; i < 100; i++) { + _queue.pushBack(_newOp(i)); - address immutable prover1 = makeAddr("Prover #1"); - DummyQueue _queueContract; + assertEq(i, _queue.head()); + assertEq(i + 1, _queue.tail()); + assertEq(1, _queue.getSize()); - function setUp() external { - _queueContract = new DummyQueue(); + PendingCommitment memory commitment = _queue.front(); + _verifyOp(i, commitment); + + PendingCommitment[] memory ops = _queue.list(); + assertEq(1, ops.length); + _verifyOp(i, ops[0]); + + PendingCommitment memory popped = _queue.popFront(); + _verifyOp(i, popped); + } } - function newOp( + function _newOp( uint256 id - ) external view returns (PendingCommitment memory) { - return PendingCommitment(id, prover1, uint64(0), uint64(0)); + ) internal pure returns (PendingCommitment memory) { + address prover = address(uint160(uint256(keccak256(abi.encodePacked("prover", id))))); + uint64 fee = uint64(uint256(keccak256(abi.encodePacked("fee", id)))); + uint64 timestamp = uint64(uint256(keccak256(abi.encodePacked("timestamp", id)))); + return PendingCommitment(id, prover, fee, timestamp); } - function testEmptyQueue() external { - PendingCommitment[] memory ops = _queueContract.list(); - assertEq(0, ops.length); + function _verifyOp( + uint256 id, + PendingCommitment memory op + ) internal { + address prover = address(uint160(uint256(keccak256(abi.encodePacked("prover", id))))); + uint64 fee = uint64(uint256(keccak256(abi.encodePacked("fee", id)))); + uint64 timestamp = uint64(uint256(keccak256(abi.encodePacked("timestamp", id)))); + + assertEq(op.commitment, id); + assertEq(op.prover, prover); + assertEq(op.fee, fee); + assertEq(op.timestamp, timestamp); } +} - function testPushBack() external { - _queueContract.pushBack(this.newOp(0)); - - assertEq(0, _queueContract.head()); - assertEq(1, _queueContract.tail()); +/** + * @dev Helper contract to test PriorityQueue library + * Without this contract forge coverage doesn't work properly + */ +contract DummyQueue { + PriorityQueue.Queue _queue; - assertEq(1, _queueContract.list().length); + function list() external view returns (PendingCommitment[] memory) { + return PriorityQueue.list(_queue); + } - _queueContract.pushBack(this.newOp(2)); + function pushBack(PendingCommitment memory _operation) external { + PriorityQueue.pushBack(_queue, _operation); + } - assertEq(2, _queueContract.list().length); + function head() external view returns (uint256) { + return _queue.head; } - - function testPopFront() external { - _queueContract.pushBack(this.newOp(0)); - _queueContract.pushBack(this.newOp(1)); - _queueContract.pushBack(this.newOp(2)); - assertEq(0, _queueContract.head()); - assertEq(3, _queueContract.tail()); + function tail() external view returns (uint256) { + return _queue.tail; + } - PendingCommitment memory first = _queueContract.popFront(); + function getSize() external view returns (uint256) { + return PriorityQueue.getSize(_queue); + } - assertEq(first.commitment, uint256(0)); + function isEmpty() external view returns (bool) { + return PriorityQueue.isEmpty(_queue); + } - assertEq(1, _queueContract.head()); - assertEq(3, _queueContract.tail()); + function front() external view returns (PendingCommitment memory) { + return PriorityQueue.front(_queue); + } - assertEq(2, _queueContract.list().length); + function popFront() external returns (PendingCommitment memory pendingCommitments) { + return PriorityQueue.popFront(_queue); } -} +} \ No newline at end of file From c32eb0bbb2b838ca87b95204b1f7ba1573bc004e Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Thu, 4 Jan 2024 16:22:44 +0300 Subject: [PATCH 12/40] Fix formatting --- src/zkbob/ZkBobPool.sol | 49 ++++++++++++++-------------- src/zkbob/utils/CustomABIDecoder.sol | 3 +- src/zkbob/utils/PriorityQueue.sol | 6 ++-- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 0c34be4..6c9c18f 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -72,7 +72,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex * @dev Timestamp of the last tree update. */ uint64 internal lastTreeUpdateTimestamp; - + /** * @dev The duration of the grace period within which only the prover who submitted the transaction * can submit the tree update proof. @@ -190,12 +190,11 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex * @return fee fee reserved for the prover who will submit the tree update proof. * @return timestamp commitment timestamp. */ - function pendingCommitment() external view returns ( - uint256 commitment, - address privilegedProver, - uint64 fee, - uint64 timestamp - ) { + function pendingCommitment() + external + view + returns (uint256 commitment, address privilegedProver, uint64 fee, uint64 timestamp) + { PendingCommitment memory op = pendingCommitments.front(); require(op.commitment != 0, "ZkBobPool: no pending commitment"); return (op.commitment, op.prover, op.fee, op.timestamp); @@ -305,11 +304,11 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex 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"); - + _appendCommitment(_transfer_out_commit(), uint64(_memo_tree_update_fee()), msg.sender); nullifiers[nullifier] = uint256(keccak256(abi.encodePacked(_transfer_out_commit(), _transfer_delta()))); - + bytes memory message = _memo_message(); // restrict memo message prefix (items count in little endian) to be < 2**16 require(bytes4(message) & 0x0000ffff == MESSAGE_PREFIX_COMMON_V1, "ZkBobPool: bad message prefix"); @@ -327,7 +326,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex uint256 treeUpdateFee = _memo_tree_update_fee(); require(treeUpdateFee >= minTreeUpdateFee, "ZkBobPool: tree update fee is too low"); - + uint256 fee = transactFee + treeUpdateFee; int256 token_amount = transfer_token_delta + int256(fee); @@ -407,7 +406,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex // we reserve the minimal tree update fee for the prover who will submit the tree update proof require(totalFee >= minTreeUpdateFee, "ZkBobPool: tree update fee is too low"); uint64 ddFee = uint64(totalFee) - minTreeUpdateFee; - + _appendCommitment(_out_commit, minTreeUpdateFee, msg.sender); bytes32 message_hash = keccak256(message); @@ -426,25 +425,28 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex /** * @dev Updates pool index and merkle tree root if the provided proof is valid and - * the proof corresponds to the pending commitment. + * the proof corresponds to the pending commitment. * A prover that submitted the transfer proof has the grace period to submit the tree update proof. * @param _commitment pending commitment to be proven. * @param _proof snark proof for tree update verifier. * @param _rootAfter new merkle tree root. */ function proveTreeUpdate( - uint256 _commitment, - uint256[8] calldata _proof, + uint256 _commitment, + uint256[8] calldata _proof, uint256 _rootAfter - ) external onlyOperator { + ) + external + onlyOperator + { PendingCommitment memory commitment = pendingCommitments.popFront(); require(commitment.commitment == _commitment, "ZkBobPool: commitment mismatch"); _validateGracePeriod(commitment.timestamp, commitment.prover); - + uint256[3] memory tree_pub = [roots[pool_index], _rootAfter, _commitment]; require(tree_verifier.verifyProof(tree_pub, _proof), "ZkBobPool: bad tree proof"); - + pool_index += 128; roots[pool_index] = _rootAfter; accumulatedFee[msg.sender] += commitment.fee; @@ -631,16 +633,13 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex * @dev Appends a commitment to the pending commitments queue. */ function _appendCommitment(uint256 _commitment, uint64 _fee, address _prover) internal { - pendingCommitments.pushBack(PendingCommitment({ - commitment: _commitment, - fee: _fee, - prover: _prover, - timestamp: uint64(block.timestamp) - })); + pendingCommitments.pushBack( + PendingCommitment({commitment: _commitment, fee: _fee, prover: _prover, timestamp: uint64(block.timestamp)}) + ); } /** - * @dev Validates either the grace period has passed or the caller + * @dev Validates either the grace period has passed or the caller * is the prover who submitted this commitment. */ function _validateGracePeriod(uint64 commitmentTimestamp, address privilegedProver) internal view { @@ -651,7 +650,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex timestamp = lastTreeUpdateTimestamp; } require( - block.timestamp > timestamp + gracePeriod || msg.sender == privilegedProver, + block.timestamp > timestamp + gracePeriod || msg.sender == privilegedProver, "ZkBobPool: prover is not allowed to submit the proof yet" ); } diff --git a/src/zkbob/utils/CustomABIDecoder.sol b/src/zkbob/utils/CustomABIDecoder.sol index 8927503..f0f3827 100644 --- a/src/zkbob/utils/CustomABIDecoder.sol +++ b/src/zkbob/utils/CustomABIDecoder.sol @@ -153,7 +153,8 @@ contract CustomABIDecoder { uint256 constant memo_tree_update_fee_mask = (1 << (memo_tree_update_fee_size * 8)) - 1; function _memo_tree_update_fee() internal pure returns (uint256 r) { - r = _loaduint256(memo_tree_update_fee_pos + memo_tree_update_fee_size - uint256_size) & memo_tree_update_fee_mask; + r = _loaduint256(memo_tree_update_fee_pos + memo_tree_update_fee_size - uint256_size) + & memo_tree_update_fee_mask; } // Withdraw specific data diff --git a/src/zkbob/utils/PriorityQueue.sol b/src/zkbob/utils/PriorityQueue.sol index eb3f0d1..b04ee9c 100644 --- a/src/zkbob/utils/PriorityQueue.sol +++ b/src/zkbob/utils/PriorityQueue.sol @@ -51,10 +51,10 @@ library PriorityQueue { _queue.tail = tail + 1; } - function list(Queue storage _queue) internal view returns ( PendingCommitment[] memory) { - PendingCommitment[] memory result = new PendingCommitment[] (_queue.getSize()); + function list(Queue storage _queue) internal view returns (PendingCommitment[] memory) { + PendingCommitment[] memory result = new PendingCommitment[](_queue.getSize()); for (uint256 index = _queue.head; index < _queue.tail; index++) { - result[index-_queue.head] = _queue.data[index]; + result[index - _queue.head] = _queue.data[index]; } return result; } From 266c118219b2adf1e400ac3d73719bc260ac91d6 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Thu, 4 Jan 2024 16:31:48 +0300 Subject: [PATCH 13/40] Fix formatting --- src/interfaces/IZkBobDirectDeposits.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/interfaces/IZkBobDirectDeposits.sol b/src/interfaces/IZkBobDirectDeposits.sol index 51caf36..72eedab 100644 --- a/src/interfaces/IZkBobDirectDeposits.sol +++ b/src/interfaces/IZkBobDirectDeposits.sol @@ -8,6 +8,7 @@ interface IZkBobDirectDeposits { Pending, // requested deposit was submitted and is pending in the queue Completed, // requested deposit was successfully processed Refunded // requested deposit was refunded to the fallback receiver + } struct DirectDeposit { From ec8b5c1ae0ddde0f3f402c75d526a901580277f1 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Thu, 4 Jan 2024 16:35:28 +0300 Subject: [PATCH 14/40] Fix formatting --- script/scripts/DeployZkBobPoolModules.s.sol | 8 +- script/scripts/Local.s.sol | 7 +- script/scripts/ZkBobPool.s.sol | 28 ++-- test/libraries/PriorityQueue.t.sol | 13 +- test/zkbob/ZkBobPool.t.sol | 142 ++++++++++++++------ test/zkbob/ZkBobPoolDecentralized.t.sol | 54 ++++---- 6 files changed, 154 insertions(+), 98 deletions(-) diff --git a/script/scripts/DeployZkBobPoolModules.s.sol b/script/scripts/DeployZkBobPoolModules.s.sol index 1715179..6914fa0 100644 --- a/script/scripts/DeployZkBobPoolModules.s.sol +++ b/script/scripts/DeployZkBobPoolModules.s.sol @@ -64,8 +64,12 @@ contract DeployZkBobPoolModules is Script, Test { vm.startBroadcast(); ZkBobPoolUSDC impl = new ZkBobPoolUSDC( - pool.pool_id(), pool.token(), pool.transfer_verifier(), pool.tree_verifier(), - pool.batch_deposit_verifier(), address(pool.direct_deposit_queue()) + pool.pool_id(), + pool.token(), + pool.transfer_verifier(), + pool.tree_verifier(), + pool.batch_deposit_verifier(), + address(pool.direct_deposit_queue()) ); Migrator mig = new Migrator(); ZkBobAccounting acc = new ZkBobAccounting(address(pool), 1_000_000_000); diff --git a/script/scripts/Local.s.sol b/script/scripts/Local.s.sol index 7f33ba5..eac51bc 100644 --- a/script/scripts/Local.s.sol +++ b/script/scripts/Local.s.sol @@ -45,12 +45,7 @@ contract DeployLocal is Script { EIP1967Proxy queueProxy = new EIP1967Proxy(tx.origin, mockImpl, ""); ZkBobPoolBOB poolImpl = new ZkBobPoolBOB( - zkBobPoolId, - address(bob), - transferVerifier, - treeVerifier, - batchDepositVerifier, - address(queueProxy) + zkBobPoolId, address(bob), transferVerifier, treeVerifier, batchDepositVerifier, address(queueProxy) ); { bytes memory initData = abi.encodeWithSelector(ZkBobPool.initialize.selector, zkBobInitialRoot); diff --git a/script/scripts/ZkBobPool.s.sol b/script/scripts/ZkBobPool.s.sol index 337c156..037ea91 100644 --- a/script/scripts/ZkBobPool.s.sol +++ b/script/scripts/ZkBobPool.s.sol @@ -53,27 +53,31 @@ contract DeployZkBobPool is Script { if (zkBobPoolType == PoolType.ETH) { vars.poolImpl = new ZkBobPoolETH( - zkBobPoolId, zkBobToken, - transferVerifier, treeVerifier, batchDepositVerifier, - address(vars.queueProxy), permit2 + zkBobPoolId, + zkBobToken, + transferVerifier, + treeVerifier, + batchDepositVerifier, + address(vars.queueProxy), + permit2 ); } else if (zkBobPoolType == PoolType.BOB) { vars.poolImpl = new ZkBobPoolBOB( - zkBobPoolId, zkBobToken, - transferVerifier, treeVerifier, batchDepositVerifier, - address(vars.queueProxy) + zkBobPoolId, zkBobToken, transferVerifier, treeVerifier, batchDepositVerifier, address(vars.queueProxy) ); } else if (zkBobPoolType == PoolType.USDC) { vars.poolImpl = new ZkBobPoolUSDC( - zkBobPoolId, zkBobToken, - transferVerifier, treeVerifier, batchDepositVerifier, - address(vars.queueProxy) + zkBobPoolId, zkBobToken, transferVerifier, treeVerifier, batchDepositVerifier, address(vars.queueProxy) ); } else if (zkBobPoolType == PoolType.ERC20) { vars.poolImpl = new ZkBobPoolERC20( - zkBobPoolId, zkBobToken, - transferVerifier, treeVerifier, batchDepositVerifier, - address(vars.queueProxy), permit2, + zkBobPoolId, + zkBobToken, + transferVerifier, + treeVerifier, + batchDepositVerifier, + address(vars.queueProxy), + permit2, vars.denominator ); } else { diff --git a/test/libraries/PriorityQueue.t.sol b/test/libraries/PriorityQueue.t.sol index dc32da4..d53f4ec 100644 --- a/test/libraries/PriorityQueue.t.sol +++ b/test/libraries/PriorityQueue.t.sol @@ -28,7 +28,7 @@ contract PriorityQueueTest is Test { assertEq(0, ops.length); } - function testPushBackPopFront() external { + function testPushBackPopFront() external { for (uint256 i = 0; i < 100; i++) { _queue.pushBack(_newOp(i)); @@ -48,19 +48,14 @@ contract PriorityQueueTest is Test { } } - function _newOp( - uint256 id - ) internal pure returns (PendingCommitment memory) { + function _newOp(uint256 id) internal pure returns (PendingCommitment memory) { address prover = address(uint160(uint256(keccak256(abi.encodePacked("prover", id))))); uint64 fee = uint64(uint256(keccak256(abi.encodePacked("fee", id)))); uint64 timestamp = uint64(uint256(keccak256(abi.encodePacked("timestamp", id)))); return PendingCommitment(id, prover, fee, timestamp); } - function _verifyOp( - uint256 id, - PendingCommitment memory op - ) internal { + function _verifyOp(uint256 id, PendingCommitment memory op) internal { address prover = address(uint160(uint256(keccak256(abi.encodePacked("prover", id))))); uint64 fee = uint64(uint256(keccak256(abi.encodePacked("fee", id)))); uint64 timestamp = uint64(uint256(keccak256(abi.encodePacked("timestamp", id)))); @@ -110,4 +105,4 @@ contract DummyQueue { function popFront() external returns (PendingCommitment memory pendingCommitments) { return PriorityQueue.popFront(_queue); } -} \ No newline at end of file +} diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 18b0f77..4571d12 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -95,27 +95,42 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { ZkBobPool impl; if (poolType == PoolType.ETH) { impl = new ZkBobPoolETH( - 0, token, - new TransferVerifierMock(), new TreeUpdateVerifierMock(), new BatchDepositVerifierMock(), - address(queueProxy), permit2 + 0, + token, + new TransferVerifierMock(), + new TreeUpdateVerifierMock(), + new BatchDepositVerifierMock(), + address(queueProxy), + permit2 ); } else if (poolType == PoolType.BOB) { impl = new ZkBobPoolBOB( - 0, token, - new TransferVerifierMock(), new TreeUpdateVerifierMock(), new BatchDepositVerifierMock(), + 0, + token, + new TransferVerifierMock(), + new TreeUpdateVerifierMock(), + new BatchDepositVerifierMock(), address(queueProxy) ); } else if (poolType == PoolType.USDC) { impl = new ZkBobPoolUSDC( - 0, token, - new TransferVerifierMock(), new TreeUpdateVerifierMock(), new BatchDepositVerifierMock(), + 0, + token, + new TransferVerifierMock(), + new TreeUpdateVerifierMock(), + new BatchDepositVerifierMock(), address(queueProxy) ); } else if (poolType == PoolType.ERC20) { impl = new ZkBobPoolERC20( - 0, token, - new TransferVerifierMock(), new TreeUpdateVerifierMock(), new BatchDepositVerifierMock(), - address(queueProxy), permit2, 1_000_000_000 + 0, + token, + new TransferVerifierMock(), + new TreeUpdateVerifierMock(), + new BatchDepositVerifierMock(), + address(queueProxy), + permit2, + 1_000_000_000 ); } @@ -187,7 +202,16 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { } } - function _encodeDeposit(int256 _amount, uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal view returns (bytes memory) { + function _encodeDeposit( + int256 _amount, + uint256 _transactFee, + uint256 _treeUpdateFee, + address prover + ) + internal + view + returns (bytes memory) + { bytes32 nullifier = bytes32(_randFR()); (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk1, ECDSA.toEthSignedMessageHash(nullifier)); bytes memory data = abi.encodePacked( @@ -202,13 +226,13 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data = abi.encodePacked(data, _randFR()); } data = abi.encodePacked( - data, - uint16(0), - uint16(72), - prover, - uint64(_transactFee / denominator), - uint64(_treeUpdateFee / denominator), - bytes4(0x01000000), + data, + uint16(0), + uint16(72), + prover, + uint64(_transactFee / denominator), + uint64(_treeUpdateFee / denominator), + bytes4(0x01000000), _randFR() ); return abi.encodePacked(data, r, uint256(s) + (v == 28 ? (1 << 255) : 0)); @@ -237,11 +261,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data = abi.encodePacked(data, _randFR()); } - data = abi.encodePacked( - data, - uint16(2), - uint16(100) - ); + data = abi.encodePacked(data, uint16(2), uint16(100)); return abi.encodePacked( data, @@ -255,21 +275,34 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { ); } - function _encodeTransfer(uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal view returns (bytes memory) { + function _encodeTransfer( + uint256 _transactFee, + uint256 _treeUpdateFee, + address prover + ) + internal + view + returns (bytes memory) + { bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, _randFR(), _randFR(), uint48(0), uint112(0), -int64(uint64((_transactFee + _treeUpdateFee) / denominator)) + ZkBobPool.transact.selector, + _randFR(), + _randFR(), + uint48(0), + uint112(0), + -int64(uint64((_transactFee + _treeUpdateFee) / denominator)) ); for (uint256 i = 0; i < 8; i++) { data = abi.encodePacked(data, _randFR()); } return abi.encodePacked( - data, - uint16(1), + data, + uint16(1), uint16(72), - prover, - uint64(_transactFee / denominator), - uint64(_treeUpdateFee / denominator), - bytes4(0x01000000), + prover, + uint64(_transactFee / denominator), + uint64(_treeUpdateFee / denominator), + bytes4(0x01000000), _randFR() ); } @@ -282,7 +315,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { function _proveTreeUpdate() internal { vm.startPrank(user2); - (uint256 commitment, , , ) = pool.pendingCommitment(); + (uint256 commitment,,,) = pool.pendingCommitment(); pool.proveTreeUpdate(commitment, _randProof(), _randFR()); vm.stopPrank(); } @@ -303,7 +336,15 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { return [_randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR(), _randFR()]; } - function _encodePermitDeposit(int256 _amount, uint256 _transactFee, uint256 _treeUpdateFee, address prover) internal returns (bytes memory) { + function _encodePermitDeposit( + int256 _amount, + uint256 _transactFee, + uint256 _treeUpdateFee, + address prover + ) + internal + returns (bytes memory) + { if (permitType == PermitType.Permit2) { vm.prank(user1); IERC20(token).approve(permit2, type(uint256).max); @@ -316,11 +357,29 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { { bytes32 digest; if (permitType == PermitType.BOBPermit) { - digest = _digestSaltedPermit(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); + digest = _digestSaltedPermit( + user1, + address(pool), + uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), + expiry, + nullifier + ); } else if (permitType == PermitType.Permit2) { - digest = _digestPermit2(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); + digest = _digestPermit2( + user1, + address(pool), + uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), + expiry, + nullifier + ); } else if (permitType == PermitType.USDCPermit) { - digest = _digestUSDCPermit(user1, address(pool), uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), expiry, nullifier); + digest = _digestUSDCPermit( + user1, + address(pool), + uint256(_amount + int256(_transactFee) + int256(_treeUpdateFee)), + expiry, + nullifier + ); } (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk1, digest); signature = abi.encodePacked(r, uint256(s) + (v == 28 ? (1 << 255) : 0)); @@ -338,11 +397,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data = abi.encodePacked(data, _randFR()); } - data = abi.encodePacked( - data, - uint16(3), - uint16(100) - ); + data = abi.encodePacked(data, uint16(3), uint16(100)); data = abi.encodePacked( data, @@ -433,7 +488,6 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { } abstract contract AbstractZkBobPoolTest is AbstractZkBobPoolTestBase { - function setUp() public override { super.setUp(); } @@ -555,7 +609,9 @@ abstract contract AbstractZkBobPoolTest is AbstractZkBobPoolTestBase { function testMultiplePermitDeposits() public { for (uint256 i = 1; i < 10; i++) { deal(address(token), user1, 0.101 ether / D * i); - bytes memory data = _encodePermitDeposit(int256(0.1 ether / D) * int256(i), 0.0005 ether / D * i, 0.0005 ether / D * i, user2); + bytes memory data = _encodePermitDeposit( + int256(0.1 ether / D) * int256(i), 0.0005 ether / D * i, 0.0005 ether / D * i, user2 + ); _transact(data); _proveTreeUpdate(); } diff --git a/test/zkbob/ZkBobPoolDecentralized.t.sol b/test/zkbob/ZkBobPoolDecentralized.t.sol index f4af4a8..c76db6d 100644 --- a/test/zkbob/ZkBobPoolDecentralized.t.sol +++ b/test/zkbob/ZkBobPoolDecentralized.t.sol @@ -11,7 +11,7 @@ import "../shared/ForkTests.t.sol"; abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBase { AllowListOperatorManager manager; - + address prover1 = makeAddr("Prover #1"); address feeReceiver1 = makeAddr("Fee Receiver #1"); @@ -22,7 +22,7 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas address[] provers = [prover1, prover2]; address[] feeReceivers = [feeReceiver1, feeReceiver2]; - + function setUp() public override { super.setUp(); @@ -39,7 +39,8 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas bytes memory data2 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover2); _transact(data2, prover2); - bytes memory data3 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, notAllowedProver); + bytes memory data3 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, notAllowedProver); _transactExpectRevert(data3, notAllowedProver, "ZkBobPool: not an operator"); manager.setAllowListEnabled(false); @@ -65,7 +66,7 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas _proveTreeUpdate(prover2); } - + function testNotAllowedProverCantUpdateTreeEvenAfterGracePeriod() public { bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); _transact(data1, prover1); @@ -77,7 +78,7 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas function testGracePeriodStartsOnlyAfterTreeUpdatingIfNewTransactSendedBeforeOldTreeUpdate() public { deal(token, user1, 100 ether / D); - + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); _transact(data1, prover1); @@ -95,7 +96,7 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.017 ether / D, 0.005 ether / D, prover1); _transact(data1, prover1); assertEq(pool.accumulatedFee(prover1), 0.017 ether / (D * denominator)); - + vm.prank(feeReceiver1); pool.withdrawFee(prover1, feeReceiver1); @@ -103,13 +104,13 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas _proveTreeUpdate(prover2); assertEq(pool.accumulatedFee(prover2), 0.005 ether / (D * denominator)); - + vm.prank(feeReceiver2); pool.withdrawFee(prover2, feeReceiver2); assertEq(pool.accumulatedFee(prover1), 0); assertEq(pool.accumulatedFee(prover2), 0); - + assertEq(IERC20(token).balanceOf(feeReceiver1), 0.017 ether / D); assertEq(IERC20(token).balanceOf(feeReceiver2), 0.005 ether / D); assertEq(IERC20(token).balanceOf(address(pool)), 0.5 ether / D); @@ -128,7 +129,7 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas function testCantTransactIfTreeUpdateFeeIsLessThenMin() public { deal(token, user1, 100 ether / D); pool.setMinTreeUpdateFee(uint64(0.01 ether / (D * denominator))); - + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.009 ether / D, prover1); _transactExpectRevert(data1, prover1, "ZkBobPool: tree update fee is too low"); @@ -185,7 +186,7 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas queue.setDirectDepositFee(uint64(1)); vm.startPrank(user1); - + _directDeposit(10 ether / D, user2, zkAddress); _directDeposit(5 ether / D, user2, zkAddress); vm.stopPrank(); @@ -193,7 +194,7 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas uint256[] memory indices = new uint256[](2); indices[0] = 0; indices[1] = 1; - + uint256 outCommitment = _randFR(); vm.prank(prover1); vm.expectRevert("ZkBobPool: tree update fee is too low"); @@ -205,7 +206,7 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas uint64 minTreeUpdateFee = uint64(0.01 ether / (D * denominator)); uint64 singleDirectDepositFee = uint64(0.1 ether / (D * denominator)); - + pool.setMinTreeUpdateFee(minTreeUpdateFee); queue.setDirectDepositFee(singleDirectDepositFee); @@ -217,11 +218,11 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas uint256[] memory indices = new uint256[](2); indices[0] = 0; indices[1] = 1; - + uint256 outCommitment = _randFR(); vm.prank(prover1); pool.appendDirectDeposits(indices, outCommitment, _randProof()); - uint64 expectedFee = uint64(singleDirectDepositFee* 2 - minTreeUpdateFee); + uint64 expectedFee = uint64(singleDirectDepositFee * 2 - minTreeUpdateFee); assertEq(expectedFee, pool.accumulatedFee(prover1)); vm.prank(prover1); @@ -254,23 +255,22 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas } function _proveTreeUpdate(address caller) internal { - (uint256 commitment, , , ) = pool.pendingCommitment(); + (uint256 commitment,,,) = pool.pendingCommitment(); vm.prank(caller); pool.proveTreeUpdate(commitment, _randProof(), _randFR()); } function _proveTreeUpdateExpectRevert(address caller, string memory expectedRevertReason) internal { - (uint256 commitment, , , ) = pool.pendingCommitment(); + (uint256 commitment,,,) = pool.pendingCommitment(); vm.expectRevert(bytes(expectedRevertReason)); vm.prank(caller); pool.proveTreeUpdate(commitment, _randProof(), _randFR()); } - function _prepareRandomDirectDeposits(uint256 offset) internal returns ( - uint256[] memory indices, - uint256 commitment, - uint256[8] memory proof - ) { + function _prepareRandomDirectDeposits(uint256 offset) + internal + returns (uint256[] memory indices, uint256 commitment, uint256[8] memory proof) + { _setUpDD(); vm.startPrank(user1); @@ -287,11 +287,13 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas } function _appendDirectDeposits( - uint256[] memory indices, - uint256 commitment, - uint256[8] memory proof, + uint256[] memory indices, + uint256 commitment, + uint256[8] memory proof, address prover - ) internal { + ) + internal + { vm.prank(prover); pool.appendDirectDeposits(indices, commitment, proof); } @@ -351,4 +353,4 @@ contract ZkBobPoolUSDCPolygonDecentralizedTest is AbstractZkBobPoolDecentralized denominator = 1; precision = 1_000_000; } -} \ No newline at end of file +} From 5d78b894751805ed037868ebf0aadcc8b72cb46e Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Fri, 5 Jan 2024 11:13:25 +0300 Subject: [PATCH 15/40] Rename transact, add calldata version --- src/zkbob/ZkBobPool.sol | 4 +++- src/zkbob/utils/CustomABIDecoder.sol | 12 ++++++++++-- test/zkbob/ZkBobPool.t.sol | 21 +++++++++++++++------ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 6c9c18f..83a9442 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -275,7 +275,9 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex * Method uses a custom ABI encoding scheme described in CustomABIDecoder. * Single transact() call performs either deposit, withdrawal or shielded transfer operation. */ - function transact() external onlyOperator minTreeUpdateFeeIsSet { + function transactV2() external onlyOperator minTreeUpdateFeeIsSet { + require(_version() == 2, "ZkBobPool: incorrect calldata version"); + address user = msg.sender; uint256 txType = _tx_type(); if (txType == 0) { diff --git a/src/zkbob/utils/CustomABIDecoder.sol b/src/zkbob/utils/CustomABIDecoder.sol index f0f3827..8b2972b 100644 --- a/src/zkbob/utils/CustomABIDecoder.sol +++ b/src/zkbob/utils/CustomABIDecoder.sol @@ -3,8 +3,6 @@ pragma solidity 0.8.15; contract CustomABIDecoder { - uint256 constant transfer_nullifier_pos = 4; - uint256 constant transfer_nullifier_size = 32; uint256 constant uint256_size = 32; function _loaduint256(uint256 pos) internal pure returns (uint256 r) { @@ -13,6 +11,16 @@ contract CustomABIDecoder { } } + uint256 constant version_pos = 4; + uint256 constant version_size = 1; + + function _version() internal pure returns (uint8 r) { + r = uint8(_loaduint256(version_pos) >> (8 * (uint256_size - version_size))); + } + + uint256 constant transfer_nullifier_pos = version_pos + version_size; + uint256 constant transfer_nullifier_size = 32; + function _transfer_nullifier() internal pure returns (uint256 r) { r = _loaduint256(transfer_nullifier_pos); } diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 4571d12..d674016 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -213,9 +213,15 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { returns (bytes memory) { bytes32 nullifier = bytes32(_randFR()); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk1, ECDSA.toEthSignedMessageHash(nullifier)); + + bytes memory permitSignature; + { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk1, ECDSA.toEthSignedMessageHash(nullifier)); + permitSignature = abi.encodePacked(r, uint256(s) + (v == 28 ? (1 << 255) : 0)); + } bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, + ZkBobPool.transactV2.selector, + uint8(2), nullifier, _randFR(), uint48(0), @@ -235,7 +241,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { bytes4(0x01000000), _randFR() ); - return abi.encodePacked(data, r, uint256(s) + (v == 28 ? (1 << 255) : 0)); + return abi.encodePacked(data, permitSignature); } function _encodeWithdrawal( @@ -250,7 +256,8 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { returns (bytes memory) { bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, + ZkBobPool.transactV2.selector, + uint8(2), _randFR(), _randFR(), uint48(0), @@ -285,7 +292,8 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { returns (bytes memory) { bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, + ZkBobPool.transactV2.selector, + uint8(2), _randFR(), _randFR(), uint48(0), @@ -386,7 +394,8 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { } bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, + ZkBobPool.transactV2.selector, + uint8(2), nullifier, _randFR(), uint48(0), From 93c29bf4c7ed6fa52a84e98adb81a632d8b86dc4 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Thu, 11 Jan 2024 13:42:19 +0300 Subject: [PATCH 16/40] Add commitment to RootUpdated event --- src/zkbob/ZkBobPool.sol | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 83a9442..609a363 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -63,6 +63,12 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex mapping(address => uint256) public accumulatedFee; + // TODO: + // Is it safe to just add deprecated gap here and + // call setTokenSeller() again after upgrade to fix storage layout? + // Additionally, it makes sense to add __gap as well. + // address private __deprecatedGap2; + /** * @dev Queue of pending commitments to be included in the Merkle Tree. */ @@ -86,6 +92,8 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex */ uint64 public minTreeUpdateFee; + // uint256[50] __gap; + event UpdateOperatorManager(address manager); event UpdateAccounting(address accounting); event UpdateRedeemer(address redeemer); @@ -94,7 +102,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex event WithdrawFee(address indexed operator, uint256 fee); event Message(uint256 indexed index, bytes32 indexed hash, bytes message); - event RootUpdated(uint256 indexed index, uint256 root); + event RootUpdated(uint256 indexed index, uint256 root, uint256 commitment); event CommitForcedExit( uint256 indexed nullifier, address operator, address to, uint256 amount, uint256 exitStart, uint256 exitEnd @@ -454,7 +462,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex accumulatedFee[msg.sender] += commitment.fee; lastTreeUpdateTimestamp = uint64(block.timestamp); - emit RootUpdated(pool_index, _rootAfter); + emit RootUpdated(pool_index, _rootAfter, _commitment); } /** From c397b69f80298cccadedbf45034b0e666ef51047 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Fri, 12 Jan 2024 14:47:14 +0300 Subject: [PATCH 17/40] Return gracePeriodEnd from pendingCommitment --- src/zkbob/ZkBobPool.sol | 9 ++++++--- test/interfaces/IZkBobPoolAdmin.sol | 2 +- test/zkbob/ZkBobPool.t.sol | 2 +- test/zkbob/ZkBobPoolDecentralized.t.sol | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 609a363..22ba0b7 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -77,7 +77,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex /** * @dev Timestamp of the last tree update. */ - uint64 internal lastTreeUpdateTimestamp; + uint64 public lastTreeUpdateTimestamp; /** * @dev The duration of the grace period within which only the prover who submitted the transaction @@ -197,15 +197,18 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex * @return privilegedProver prover that can submit the tree update proof within the grace period. * @return fee fee reserved for the prover who will submit the tree update proof. * @return timestamp commitment timestamp. + * @return gracePeriodEnd timestamp when the grace period ends. */ function pendingCommitment() external view - returns (uint256 commitment, address privilegedProver, uint64 fee, uint64 timestamp) + returns (uint256 commitment, address privilegedProver, uint64 fee, uint64 timestamp, uint64 gracePeriodEnd) { PendingCommitment memory op = pendingCommitments.front(); require(op.commitment != 0, "ZkBobPool: no pending commitment"); - return (op.commitment, op.prover, op.fee, op.timestamp); + uint64 gracePeriodStart = op.timestamp > lastTreeUpdateTimestamp ? op.timestamp : lastTreeUpdateTimestamp; + gracePeriodEnd = gracePeriodStart + gracePeriod; + return (op.commitment, op.prover, op.fee, op.timestamp, gracePeriodEnd); } /** diff --git a/test/interfaces/IZkBobPoolAdmin.sol b/test/interfaces/IZkBobPoolAdmin.sol index c71a906..0e5e6db 100644 --- a/test/interfaces/IZkBobPoolAdmin.sol +++ b/test/interfaces/IZkBobPoolAdmin.sol @@ -11,7 +11,7 @@ interface IZkBobPoolAdmin { function pool_index() external view returns (uint256); - function pendingCommitment() external view returns (uint256, address, uint64, uint64); + function pendingCommitment() external view returns (uint256, address, uint64, uint64, uint64); function initialize(uint256 _root) external; diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index d674016..a2cd4fd 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -323,7 +323,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { function _proveTreeUpdate() internal { vm.startPrank(user2); - (uint256 commitment,,,) = pool.pendingCommitment(); + (uint256 commitment,,,,) = pool.pendingCommitment(); pool.proveTreeUpdate(commitment, _randProof(), _randFR()); vm.stopPrank(); } diff --git a/test/zkbob/ZkBobPoolDecentralized.t.sol b/test/zkbob/ZkBobPoolDecentralized.t.sol index c76db6d..351e1d0 100644 --- a/test/zkbob/ZkBobPoolDecentralized.t.sol +++ b/test/zkbob/ZkBobPoolDecentralized.t.sol @@ -255,13 +255,13 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas } function _proveTreeUpdate(address caller) internal { - (uint256 commitment,,,) = pool.pendingCommitment(); + (uint256 commitment,,,,) = pool.pendingCommitment(); vm.prank(caller); pool.proveTreeUpdate(commitment, _randProof(), _randFR()); } function _proveTreeUpdateExpectRevert(address caller, string memory expectedRevertReason) internal { - (uint256 commitment,,,) = pool.pendingCommitment(); + (uint256 commitment,,,,) = pool.pendingCommitment(); vm.expectRevert(bytes(expectedRevertReason)); vm.prank(caller); pool.proveTreeUpdate(commitment, _randProof(), _randFR()); From 20382b710d11db2db46773bd8816de59e72b6140 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Tue, 16 Jan 2024 17:15:48 +0300 Subject: [PATCH 18/40] Add memo message size --- src/zkbob/utils/CustomABIDecoder.sol | 6 +++- test/zkbob/ZkBobPool.t.sol | 53 +++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/zkbob/utils/CustomABIDecoder.sol b/src/zkbob/utils/CustomABIDecoder.sol index 8b2972b..cca968c 100644 --- a/src/zkbob/utils/CustomABIDecoder.sol +++ b/src/zkbob/utils/CustomABIDecoder.sol @@ -131,10 +131,14 @@ contract CustomABIDecoder { } } + uint256 constant memo_message_size_size = 2; + uint256 constant memo_message_size_mask = (1 << (memo_message_size_size * 8)) - 1; + function _memo_message() internal pure returns (bytes calldata r) { uint256 memo_fixed_size = _memo_fixed_size(); uint256 offset = memo_data_pos + memo_fixed_size; - uint256 length = _memo_data_size() - memo_fixed_size; + uint256 length = _loaduint256(offset + memo_message_size_size - uint256_size) & memo_message_size_mask; + offset += memo_message_size_size; assembly { r.offset := offset r.length := length diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index a2cd4fd..3bedce8 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -234,10 +234,11 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data = abi.encodePacked( data, uint16(0), - uint16(72), + uint16(74), prover, uint64(_transactFee / denominator), uint64(_treeUpdateFee / denominator), + uint16(36), // memo message size bytes4(0x01000000), _randFR() ); @@ -268,7 +269,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data = abi.encodePacked(data, _randFR()); } - data = abi.encodePacked(data, uint16(2), uint16(100)); + data = abi.encodePacked(data, uint16(2), uint16(102)); return abi.encodePacked( data, @@ -277,6 +278,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { uint64(0.005 ether / D / denominator), uint64(_nativeAmount / denominator), _to, + uint16(36), // memo message size bytes4(0x01000000), _randFR() ); @@ -306,10 +308,11 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { return abi.encodePacked( data, uint16(1), - uint16(72), + uint16(74), prover, uint64(_transactFee / denominator), uint64(_treeUpdateFee / denominator), + uint16(36), // memo message size bytes4(0x01000000), _randFR() ); @@ -406,7 +409,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data = abi.encodePacked(data, _randFR()); } - data = abi.encodePacked(data, uint16(3), uint16(100)); + data = abi.encodePacked(data, uint16(3), uint16(102)); data = abi.encodePacked( data, @@ -415,6 +418,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { uint64(_treeUpdateFee / denominator), uint64(expiry), user1, + uint16(36), // memo message size bytes4(0x01000000), _randFR() ); @@ -1061,6 +1065,47 @@ abstract contract AbstractZkBobPoolTest is AbstractZkBobPoolTestBase { // 1e18 energy * (1e16 * 1e12 / 1e18) / 2e5 / 28 ~= 1785e18 reward tokens assertApproxEqAbs(rewardToken.balanceOf(user1), 1785 ether, 200 ether); } + + function testTransactMessageEvent() public { + bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); + bytes memory message = _slice(data, 423, 36); + vm.expectEmit(true, false, false, true); + emit Message(128, bytes32(0), message); + _transact(data); + _proveTreeUpdate(); + + vm.prank(user1); + IERC20(token).approve(address(pool), 0.11 ether / D); + + bytes memory data1 = _encodeDeposit(int256(0.1 ether / D), 0.005 ether / D, 0.005 ether / D, user2); + bytes memory message1 = _slice(data1, 395, 36); + vm.expectEmit(true, false, false, true); + emit Message(256, bytes32(0), message1); + _transact(data1); + _proveTreeUpdate(); + + bytes memory data2 = _encodeTransfer(0.005 ether / D, 0.005 ether / D, user2); + bytes memory message2 = _slice(data2, 395, 36); + vm.expectEmit(true, false, false, true); + emit Message(384, bytes32(0), message2); + _transact(data2); + _proveTreeUpdate(); + + bytes memory data3 = _encodeWithdrawal(user1, 0.1 ether / D, 0, 0, user2); + bytes memory message3 = _slice(data3, 423, 36); + vm.expectEmit(true, false, false, true); + emit Message(512, bytes32(0), message3); + _transact(data3); + _proveTreeUpdate(); + } + + function _slice(bytes memory data, uint256 start, uint256 length) internal pure returns (bytes memory) { + bytes memory res = new bytes(length); + for (uint256 i = 0; i < length; i++) { + res[i] = data[start + i]; + } + return res; + } } contract ZkBobPoolBOBPolygonTest is AbstractZkBobPoolTest, AbstractPolygonForkTest { From b8356d32885dc0b84af95456521eeedecbe186d6 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Wed, 17 Jan 2024 12:03:03 +0300 Subject: [PATCH 19/40] Add extra data to tests --- test/zkbob/ZkBobPool.t.sol | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 3bedce8..24a011a 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -234,13 +234,11 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data = abi.encodePacked( data, uint16(0), - uint16(74), + uint16(84), prover, uint64(_transactFee / denominator), uint64(_treeUpdateFee / denominator), - uint16(36), // memo message size - bytes4(0x01000000), - _randFR() + _memoMessageAndExtraData() ); return abi.encodePacked(data, permitSignature); } @@ -269,7 +267,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data = abi.encodePacked(data, _randFR()); } - data = abi.encodePacked(data, uint16(2), uint16(102)); + data = abi.encodePacked(data, uint16(2), uint16(112)); return abi.encodePacked( data, @@ -278,9 +276,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { uint64(0.005 ether / D / denominator), uint64(_nativeAmount / denominator), _to, - uint16(36), // memo message size - bytes4(0x01000000), - _randFR() + _memoMessageAndExtraData() ); } @@ -308,13 +304,20 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { return abi.encodePacked( data, uint16(1), - uint16(74), + uint16(84), prover, uint64(_transactFee / denominator), uint64(_treeUpdateFee / denominator), + _memoMessageAndExtraData() + ); + } + + function _memoMessageAndExtraData() internal view returns (bytes memory) { + return abi.encodePacked( uint16(36), // memo message size bytes4(0x01000000), - _randFR() + _randFR(), + bytes("extra data") ); } @@ -409,7 +412,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data = abi.encodePacked(data, _randFR()); } - data = abi.encodePacked(data, uint16(3), uint16(102)); + data = abi.encodePacked(data, uint16(3), uint16(112)); data = abi.encodePacked( data, @@ -418,9 +421,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { uint64(_treeUpdateFee / denominator), uint64(expiry), user1, - uint16(36), // memo message size - bytes4(0x01000000), - _randFR() + _memoMessageAndExtraData() ); return abi.encodePacked(data, signature); } From 8250dcee8ee91b6efcd3d041520db55df85d436d Mon Sep 17 00:00:00 2001 From: AllFi Date: Wed, 17 Jan 2024 16:16:25 +0300 Subject: [PATCH 20/40] Support new message prefix --- src/zkbob/ZkBobPool.sol | 13 +++++++++-- test/zkbob/ZkBobPool.t.sol | 47 +++++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 22ba0b7..932ddb3 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -36,6 +36,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex uint256 internal constant MAX_POOL_ID = 0xffffff; bytes4 internal constant MESSAGE_PREFIX_COMMON_V1 = 0x00000000; + bytes4 internal constant MESSAGE_PREFIX_COMMON_V2 = 0x00000002; uint256 internal constant FORCED_EXIT_MIN_DELAY = 1 hours; uint256 internal constant FORCED_EXIT_MAX_DELAY = 24 hours; @@ -323,8 +324,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex nullifiers[nullifier] = uint256(keccak256(abi.encodePacked(_transfer_out_commit(), _transfer_delta()))); bytes memory message = _memo_message(); - // restrict memo message prefix (items count in little endian) to be < 2**16 - require(bytes4(message) & 0x0000ffff == MESSAGE_PREFIX_COMMON_V1, "ZkBobPool: bad message prefix"); + require(_isValidPrefix(bytes4(message) & 0x0000ffff), "ZkBobPool: bad message prefix"); bytes32 message_hash = keccak256(message); bytes32 _all_messages_hash = keccak256(abi.encodePacked(all_messages_hash, message_hash)); all_messages_hash = _all_messages_hash; @@ -667,4 +667,13 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex "ZkBobPool: prover is not allowed to submit the proof yet" ); } + + /** + * @dev Tells if given message prefix is valid. + * @param _prefix prefix to check. + * @return true, if prefix is valid. + */ + function _isValidPrefix(bytes4 _prefix) internal pure returns (bool) { + return _prefix == MESSAGE_PREFIX_COMMON_V1 || _prefix == MESSAGE_PREFIX_COMMON_V2; + } } diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 24a011a..74127e7 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -238,7 +238,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { prover, uint64(_transactFee / denominator), uint64(_treeUpdateFee / denominator), - _memoMessageAndExtraData() + _memoMessageAndExtraData(bytes2(0)) ); return abi.encodePacked(data, permitSignature); } @@ -276,14 +276,27 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { uint64(0.005 ether / D / denominator), uint64(_nativeAmount / denominator), _to, - _memoMessageAndExtraData() + _memoMessageAndExtraData(bytes2(0)) ); } function _encodeTransfer( uint256 _transactFee, uint256 _treeUpdateFee, - address prover + address _prover + ) + internal + view + returns (bytes memory) + { + return _encodeTransferWithPrefix(_transactFee, _treeUpdateFee, _prover, bytes2(0)); + } + + function _encodeTransferWithPrefix( + uint256 _transactFee, + uint256 _treeUpdateFee, + address _prover, + bytes2 _prefix ) internal view @@ -305,17 +318,18 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data, uint16(1), uint16(84), - prover, + _prover, uint64(_transactFee / denominator), uint64(_treeUpdateFee / denominator), - _memoMessageAndExtraData() + _memoMessageAndExtraData(_prefix) ); } - function _memoMessageAndExtraData() internal view returns (bytes memory) { + function _memoMessageAndExtraData(bytes2 _prefix) internal view returns (bytes memory) { return abi.encodePacked( uint16(36), // memo message size - bytes4(0x01000000), + bytes2(0x0100), + _prefix, _randFR(), bytes("extra data") ); @@ -421,7 +435,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { uint64(_treeUpdateFee / denominator), uint64(expiry), user1, - _memoMessageAndExtraData() + _memoMessageAndExtraData(bytes2(0)) ); return abi.encodePacked(data, signature); } @@ -1100,6 +1114,23 @@ abstract contract AbstractZkBobPoolTest is AbstractZkBobPoolTestBase { _proveTreeUpdate(); } + function testTransactAcceptsOnlyValidPrefixes() public { + bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); + _transact(data); + + data = _encodeTransferWithPrefix(0.005 ether / D, 0.005 ether / D, user2, bytes2(0x0000)); + _transact(data); + + data = _encodeTransferWithPrefix(0.005 ether / D, 0.005 ether / D, user2, bytes2(0x0002)); + _transact(data); + + data = _encodeTransferWithPrefix(0.005 ether / D, 0.005 ether / D, user2, bytes2(0x0001)); + _transactReverted(data, "ZkBobPool: bad message prefix"); + + data = _encodeTransferWithPrefix(0.005 ether / D, 0.005 ether / D, user2, bytes2(0x1234)); + _transactReverted(data, "ZkBobPool: bad message prefix"); + } + function _slice(bytes memory data, uint256 start, uint256 length) internal pure returns (bytes memory) { bytes memory res = new bytes(length); for (uint256 i = 0; i < length; i++) { From 6fe228e54d1bba3bec1416c1fbaf7d91d6844a38 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Wed, 24 Jan 2024 14:26:48 +0300 Subject: [PATCH 21/40] Remove forced exit related code --- src/interfaces/IZkBobAccounting.sol | 3 +- src/zkbob/ZkBobPool.sol | 144 +--------------------------- src/zkbob/utils/ZkBobAccounting.sol | 5 - test/interfaces/IZkBobPoolAdmin.sol | 24 ----- test/zkbob/ZkBobPool.t.sol | 79 --------------- 5 files changed, 2 insertions(+), 253 deletions(-) diff --git a/src/interfaces/IZkBobAccounting.sol b/src/interfaces/IZkBobAccounting.sol index f66bed3..24cb3f9 100644 --- a/src/interfaces/IZkBobAccounting.sol +++ b/src/interfaces/IZkBobAccounting.sol @@ -8,8 +8,7 @@ interface IZkBobAccounting { enum TxType { Common, DirectDeposit, - AppendDirectDeposits, - ForcedExit + AppendDirectDeposits } struct Limits { diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 932ddb3..1e53b3d 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -37,8 +37,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex uint256 internal constant MAX_POOL_ID = 0xffffff; bytes4 internal constant MESSAGE_PREFIX_COMMON_V1 = 0x00000000; bytes4 internal constant MESSAGE_PREFIX_COMMON_V2 = 0x00000002; - uint256 internal constant FORCED_EXIT_MIN_DELAY = 1 hours; - uint256 internal constant FORCED_EXIT_MAX_DELAY = 24 hours; uint256 internal immutable TOKEN_DENOMINATOR; uint256 internal constant TOKEN_NUMERATOR = 1; @@ -50,8 +48,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex address public immutable token; IZkBobDirectDepositQueue public immutable direct_deposit_queue; - uint256[2] private __deprecatedGap; - mapping(uint256 => bytes32) public committedForcedExits; + uint256[3] private __deprecatedGap; IEnergyRedeemer public redeemer; IZkBobAccounting public accounting; uint96 public pool_index; @@ -105,12 +102,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex event Message(uint256 indexed index, bytes32 indexed hash, bytes message); event RootUpdated(uint256 indexed index, uint256 root, uint256 commitment); - 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( uint256 __pool_id, address _token, @@ -468,116 +459,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex emit RootUpdated(pool_index, _rootAfter, _commitment); } - /** - * @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. - * @param _operator address that is allowed to call executeForcedExit, or address(0) if permissionless. - * @param _to withdrawn funds receiver. - * @param _amount total account balance to withdraw. - * @param _index index of the merkle root used within proof. - * @param _nullifier transfer nullifier to be used for withdrawal. - * @param _out_commit out commitment for empty list of output notes. - * @param _transfer_proof snark proof for transfer verifier. - */ - function commitForcedExit( - address _operator, - address _to, - uint256 _amount, - uint256 _index, - uint256 _nullifier, - uint256 _out_commit, - uint256[8] memory _transfer_proof - ) - external - { - require( - _amount > 0 && _amount % TOKEN_NUMERATOR == 0 && _amount <= 1 << 63, "ZkBobPool: incorrect token amount" - ); - require(_index < type(uint48).max, "ZkBobPool: index too large"); - - 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, - _nullifier, - _out_commit, - (pool_id << 224) + (_index << 176) + uint64(-int64(uint64(_amount))), - uint256(keccak256(abi.encodePacked(_to))) % R - ]; - require(transfer_verifier.verifyProof(transfer_pub, _transfer_proof), "ZkBobPool: bad transfer proof"); - - committedForcedExits[_nullifier] = _hashForcedExit( - _operator, _to, _amount, block.timestamp + FORCED_EXIT_MIN_DELAY, block.timestamp + FORCED_EXIT_MAX_DELAY - ); - - emit CommitForcedExit( - _nullifier, - _operator, - _to, - _amount, - block.timestamp + FORCED_EXIT_MIN_DELAY, - block.timestamp + FORCED_EXIT_MAX_DELAY - ); - } - - /** - * @dev Performs a forced withdrawal by irreversibly killing an account. - * Callable only by the operator, if set during latest call to the commitForcedExit. - * Account cannot be recovered after such forced exit. - * 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. - * @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, - bool _cancel - ) - external - { - require(nullifiers[_nullifier] == 0, "ZkBobPool: doublespend detected"); - require( - 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"); - - (IZkBobAccounting acc, uint96 poolIndex) = (accounting, pool_index); - if (address(acc) != address(0)) { - acc.recordOperation(IZkBobAccounting.TxType.ForcedExit, address(0), int256(_amount)); - } - nullifiers[_nullifier] = poolIndex | uint256(0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead0000000000000000); - - IERC20(token).safeTransfer(_to, _amount * TOKEN_DENOMINATOR / TOKEN_NUMERATOR); - - emit ForcedExit(poolIndex, _nullifier, _to, _amount); - } - /** * @dev Records submitted direct deposit into the users limits. * Callable only by the direct deposit queue. @@ -610,29 +491,6 @@ 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. diff --git a/src/zkbob/utils/ZkBobAccounting.sol b/src/zkbob/utils/ZkBobAccounting.sol index eeb2975..a7105f3 100644 --- a/src/zkbob/utils/ZkBobAccounting.sol +++ b/src/zkbob/utils/ZkBobAccounting.sol @@ -294,11 +294,6 @@ contract ZkBobAccounting is IZkBobAccounting, Ownable { _recordDirectDeposit(_user, uint256(_txAmount)); return; } - if (_txType == IZkBobAccounting.TxType.ForcedExit) { - require(_txAmount > 0, "ZkBobAccounting: negative amount"); - slot1.tvl -= uint72(uint256(_txAmount)); - return; - } Slot0 memory s0 = slot0; Slot1 memory s1 = slot1; diff --git a/test/interfaces/IZkBobPoolAdmin.sol b/test/interfaces/IZkBobPoolAdmin.sol index 0e5e6db..813a1df 100644 --- a/test/interfaces/IZkBobPoolAdmin.sol +++ b/test/interfaces/IZkBobPoolAdmin.sol @@ -35,30 +35,6 @@ interface IZkBobPoolAdmin { function proveTreeUpdate(uint256, uint256[8] memory, uint256) external; - function committedForcedExits(uint256 _nullifier) external view returns (bytes32); - - function commitForcedExit( - address _operator, - address _to, - uint256 _amount, - uint256 _index, - uint256 _nullifier, - uint256 _out_commit, - uint256[8] memory _transfer_proof - ) - external; - - function executeForcedExit( - uint256 _nullifier, - address _operator, - address _to, - uint256 _amount, - uint256 _exitStart, - uint256 _exitEnd, - bool _cancel - ) - external; - function appendDirectDeposits( uint256[] calldata _indices, uint256 _out_commit, diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 74127e7..e4ae3f2 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -684,85 +684,6 @@ abstract contract AbstractZkBobPoolTest is AbstractZkBobPoolTestBase { assertEq(IERC20(token).balanceOf(user3), 0.02 ether / D); } - function testForcedExit() public { - bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); - _transact(data); - _proveTreeUpdate(); - - 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), 0); - - vm.expectRevert("ZkBobPool: invalid forced exit"); - 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, false); - - vm.expectRevert("ZkBobPool: invalid caller"); - 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, false); - - skip(25 hours); - vm.expectRevert("ZkBobPool: exit not allowed"); - 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, false); - - vm.expectRevert("ZkBobPool: doublespend detected"); - 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); - - vm.expectRevert("ZkBobPool: doublespend detected"); - pool.commitForcedExit(user2, user2, 0.4 ether / D / denominator, 128, nullifier, _randFR(), _randProof()); - - vm.stopPrank(); - } - - function testCancelForcedExit() public { - bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); - _transact(data); - _proveTreeUpdate(); - - 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.005 ether / D, 0.005 ether / D, user2); _transact(data1); From 61b2fbd4b5d29becfae8952a854e3ac732d40c0c Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Wed, 31 Jan 2024 15:42:08 +0300 Subject: [PATCH 22/40] Update src/zkbob/utils/PriorityQueue.sol Co-authored-by: Kirill Fedoseev --- src/zkbob/utils/PriorityQueue.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zkbob/utils/PriorityQueue.sol b/src/zkbob/utils/PriorityQueue.sol index b04ee9c..3c6dbeb 100644 --- a/src/zkbob/utils/PriorityQueue.sol +++ b/src/zkbob/utils/PriorityQueue.sol @@ -23,7 +23,7 @@ library PriorityQueue { using PriorityQueue for Queue; /// @notice Container that stores pending commitments - /// @param data The inner mapping that saves poending commitment by its index + /// @param data The inner mapping that saves pending commitment by its index /// @param head The pointer to the first unprocessed pending commitment, equal to the tail if the queue is empty /// @param tail The pointer to the free slot struct Queue { From ecc926b6341fcbefec73e72c9846b161f270934f Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Wed, 31 Jan 2024 15:52:15 +0300 Subject: [PATCH 23/40] Refactor AllowListOperatorManager --- .../manager/AllowListOperatorManager.sol | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/zkbob/manager/AllowListOperatorManager.sol b/src/zkbob/manager/AllowListOperatorManager.sol index 2a8e271..bc28c48 100644 --- a/src/zkbob/manager/AllowListOperatorManager.sol +++ b/src/zkbob/manager/AllowListOperatorManager.sol @@ -29,12 +29,12 @@ contract AllowListOperatorManager is IOperatorManager, Ownable { } constructor(address[] memory _operators, address[] memory _feeReceivers, bool _allowListEnabled) Ownable() { + require(_operators.length == _feeReceivers.length, "OperatorManager: arrays length mismatch"); + allowListEnabled = _allowListEnabled; - bool[] memory _allowed = new bool[](_operators.length); for (uint256 i = 0; i < _operators.length; i++) { - _allowed[i] = true; + _setOperator(_operators[i], true, _feeReceivers[i]); } - _setOperators(_operators, _allowed, _feeReceivers); } /** @@ -77,7 +77,12 @@ contract AllowListOperatorManager is IOperatorManager, Ownable { external onlyOwner { - _setOperators(_operators, _allowed, _feeReceivers); + require(_operators.length == _feeReceivers.length, "OperatorManager: arrays length mismatch"); + require(_operators.length == _allowed.length, "OperatorManager: arrays length mismatch"); + + for (uint256 i = 0; i < _operators.length; i++) { + _setOperator(_operators[i], _allowed[i], _feeReceivers[i]); + } } /** @@ -90,24 +95,9 @@ contract AllowListOperatorManager is IOperatorManager, Ownable { emit UpdateOperator(msg.sender, _feeReceiver, true); } - function _setOperators( - address[] memory _operators, - bool[] memory _allowed, - address[] memory _feeReceivers - ) - internal - { - require(_operators.length == _feeReceivers.length, "OperatorManager: arrays length mismatch"); - require(_operators.length == _allowed.length, "OperatorManager: arrays length mismatch"); - - for (uint256 i = 0; i < _operators.length; i++) { - _setOperator(_operators[i], _allowed[i], _feeReceivers[i]); - } - } - function _setOperator(address _operator, bool _allowed, address _feeReceiver) internal nonZeroAddress(_operator) { operators[_operator] = _allowed; - if (_feeReceiver != address(0) && _allowed) { + if (_allowed) { operatorFeeReceiver[_operator] = _feeReceiver; } emit UpdateOperator(_operator, operatorFeeReceiver[_operator], _allowed); From 6d16f95b0ac5a7bbefdf566e022423d8e7062308 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Thu, 1 Feb 2024 13:46:51 +0300 Subject: [PATCH 24/40] Rename PriorityQueue, add copyright, uint32 timestamp --- LICENSE-MIT | 1 + src/zkbob/ZkBobPool.sol | 8 +++---- .../manager/AllowListOperatorManager.sol | 2 +- .../utils/{PriorityQueue.sol => Queue.sol} | 22 +++++++++---------- .../{PriorityQueue.t.sol => Queue.t.sol} | 20 ++++++++--------- 5 files changed, 27 insertions(+), 26 deletions(-) rename src/zkbob/utils/{PriorityQueue.sol => Queue.sol} (77%) rename test/libraries/{PriorityQueue.t.sol => Queue.t.sol} (84%) diff --git a/LICENSE-MIT b/LICENSE-MIT index 82e8ac9..4ac1c7b 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,6 +1,7 @@ MIT License Copyright (c) 2021 ZeroPool +Copyright (c) 2019 Matter Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 1e53b3d..1b2b76c 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -24,7 +24,7 @@ import "../utils/Ownable.sol"; import "../proxy/EIP1967Admin.sol"; import "../interfaces/IEnergyRedeemer.sol"; import "../utils/ExternalSload.sol"; -import {PriorityQueue, PendingCommitment} from "./utils/PriorityQueue.sol"; +import {Queue, PendingCommitment} from "./utils/Queue.sol"; /** * @title ZkBobPool @@ -32,7 +32,7 @@ import {PriorityQueue, PendingCommitment} from "./utils/PriorityQueue.sol"; */ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, ExternalSload { using SafeERC20 for IERC20; - using PriorityQueue for PriorityQueue.Queue; + using Queue for Queue.CommitmentQueue; uint256 internal constant MAX_POOL_ID = 0xffffff; bytes4 internal constant MESSAGE_PREFIX_COMMON_V1 = 0x00000000; @@ -70,7 +70,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex /** * @dev Queue of pending commitments to be included in the Merkle Tree. */ - PriorityQueue.Queue internal pendingCommitments; + Queue.CommitmentQueue internal pendingCommitments; /** * @dev Timestamp of the last tree update. @@ -505,7 +505,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex */ function _appendCommitment(uint256 _commitment, uint64 _fee, address _prover) internal { pendingCommitments.pushBack( - PendingCommitment({commitment: _commitment, fee: _fee, prover: _prover, timestamp: uint64(block.timestamp)}) + PendingCommitment({commitment: _commitment, fee: _fee, prover: _prover, timestamp: uint32(block.timestamp)}) ); } diff --git a/src/zkbob/manager/AllowListOperatorManager.sol b/src/zkbob/manager/AllowListOperatorManager.sol index bc28c48..b5b4d01 100644 --- a/src/zkbob/manager/AllowListOperatorManager.sol +++ b/src/zkbob/manager/AllowListOperatorManager.sol @@ -30,7 +30,7 @@ contract AllowListOperatorManager is IOperatorManager, Ownable { constructor(address[] memory _operators, address[] memory _feeReceivers, bool _allowListEnabled) Ownable() { require(_operators.length == _feeReceivers.length, "OperatorManager: arrays length mismatch"); - + allowListEnabled = _allowListEnabled; for (uint256 i = 0; i < _operators.length; i++) { _setOperator(_operators[i], true, _feeReceivers[i]); diff --git a/src/zkbob/utils/PriorityQueue.sol b/src/zkbob/utils/Queue.sol similarity index 77% rename from src/zkbob/utils/PriorityQueue.sol rename to src/zkbob/utils/Queue.sol index 3c6dbeb..daebe6e 100644 --- a/src/zkbob/utils/PriorityQueue.sol +++ b/src/zkbob/utils/Queue.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// Copyright (c) 2019 Matter Labs pragma solidity ^0.8.13; @@ -13,37 +14,36 @@ struct PendingCommitment { uint256 commitment; address prover; uint64 fee; - uint64 timestamp; + uint32 timestamp; } -/// @author Matter Labs /// @dev The library provides the API to interact with the priority queue container /// @dev Order of processing operations from queue - FIFO (Fist in - first out) -library PriorityQueue { - using PriorityQueue for Queue; +library Queue { + using Queue for CommitmentQueue; /// @notice Container that stores pending commitments /// @param data The inner mapping that saves pending commitment by its index /// @param head The pointer to the first unprocessed pending commitment, equal to the tail if the queue is empty /// @param tail The pointer to the free slot - struct Queue { + struct CommitmentQueue { mapping(uint256 => PendingCommitment) data; uint256 tail; uint256 head; } /// @return The total number of unprocessed pending commitments in a priority queue - function getSize(Queue storage _queue) internal view returns (uint256) { + function getSize(CommitmentQueue storage _queue) internal view returns (uint256) { return uint256(_queue.tail - _queue.head); } /// @return Whether the priority queue contains no pending commitments - function isEmpty(Queue storage _queue) internal view returns (bool) { + function isEmpty(CommitmentQueue storage _queue) internal view returns (bool) { return _queue.tail == _queue.head; } /// @notice Add the pending commitment to the end of the priority queue - function pushBack(Queue storage _queue, PendingCommitment memory _commitment) internal { + function pushBack(CommitmentQueue storage _queue, PendingCommitment memory _commitment) internal { // Save value into the stack to avoid double reading from the storage uint256 tail = _queue.tail; @@ -51,7 +51,7 @@ library PriorityQueue { _queue.tail = tail + 1; } - function list(Queue storage _queue) internal view returns (PendingCommitment[] memory) { + function list(CommitmentQueue storage _queue) internal view returns (PendingCommitment[] memory) { PendingCommitment[] memory result = new PendingCommitment[](_queue.getSize()); for (uint256 index = _queue.head; index < _queue.tail; index++) { result[index - _queue.head] = _queue.data[index]; @@ -60,7 +60,7 @@ library PriorityQueue { } /// @return The first unprocessed pending commitment from the queue - function front(Queue storage _queue) internal view returns (PendingCommitment memory) { + function front(CommitmentQueue storage _queue) internal view returns (PendingCommitment memory) { require(!_queue.isEmpty(), "ZkBobPool: queue is empty"); // priority queue is empty return _queue.data[_queue.head]; @@ -68,7 +68,7 @@ library PriorityQueue { /// @notice Remove the first unprocessed pending commitment from the queue /// @return pendingCommitment that was popped from the priority queue - function popFront(Queue storage _queue) internal returns (PendingCommitment memory pendingCommitment) { + function popFront(CommitmentQueue storage _queue) internal returns (PendingCommitment memory pendingCommitment) { require(!_queue.isEmpty(), "ZkBobPool: queue is empty"); // priority queue is empty // Save value into the stack to avoid double reading from the storage diff --git a/test/libraries/PriorityQueue.t.sol b/test/libraries/Queue.t.sol similarity index 84% rename from test/libraries/PriorityQueue.t.sol rename to test/libraries/Queue.t.sol index d53f4ec..e8becd6 100644 --- a/test/libraries/PriorityQueue.t.sol +++ b/test/libraries/Queue.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.15; import "forge-std/Test.sol"; -import {PriorityQueue, PendingCommitment} from "../../src/zkbob/utils/PriorityQueue.sol"; +import {Queue, PendingCommitment} from "../../src/zkbob/utils/Queue.sol"; import "forge-std/console.sol"; contract PriorityQueueTest is Test { @@ -51,14 +51,14 @@ contract PriorityQueueTest is Test { function _newOp(uint256 id) internal pure returns (PendingCommitment memory) { address prover = address(uint160(uint256(keccak256(abi.encodePacked("prover", id))))); uint64 fee = uint64(uint256(keccak256(abi.encodePacked("fee", id)))); - uint64 timestamp = uint64(uint256(keccak256(abi.encodePacked("timestamp", id)))); + uint32 timestamp = uint32(uint256(keccak256(abi.encodePacked("timestamp", id)))); return PendingCommitment(id, prover, fee, timestamp); } function _verifyOp(uint256 id, PendingCommitment memory op) internal { address prover = address(uint160(uint256(keccak256(abi.encodePacked("prover", id))))); uint64 fee = uint64(uint256(keccak256(abi.encodePacked("fee", id)))); - uint64 timestamp = uint64(uint256(keccak256(abi.encodePacked("timestamp", id)))); + uint32 timestamp = uint32(uint256(keccak256(abi.encodePacked("timestamp", id)))); assertEq(op.commitment, id); assertEq(op.prover, prover); @@ -72,14 +72,14 @@ contract PriorityQueueTest is Test { * Without this contract forge coverage doesn't work properly */ contract DummyQueue { - PriorityQueue.Queue _queue; + Queue.CommitmentQueue _queue; function list() external view returns (PendingCommitment[] memory) { - return PriorityQueue.list(_queue); + return Queue.list(_queue); } function pushBack(PendingCommitment memory _operation) external { - PriorityQueue.pushBack(_queue, _operation); + Queue.pushBack(_queue, _operation); } function head() external view returns (uint256) { @@ -91,18 +91,18 @@ contract DummyQueue { } function getSize() external view returns (uint256) { - return PriorityQueue.getSize(_queue); + return Queue.getSize(_queue); } function isEmpty() external view returns (bool) { - return PriorityQueue.isEmpty(_queue); + return Queue.isEmpty(_queue); } function front() external view returns (PendingCommitment memory) { - return PriorityQueue.front(_queue); + return Queue.front(_queue); } function popFront() external returns (PendingCommitment memory pendingCommitments) { - return PriorityQueue.popFront(_queue); + return Queue.popFront(_queue); } } From 118287802305a9331f529cb19051b1e979220f9b Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Thu, 1 Feb 2024 14:03:32 +0300 Subject: [PATCH 25/40] Remove minTreeUpdateFeeIsSet modifier --- src/zkbob/ZkBobPool.sol | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 1b2b76c..07c6de4 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -136,14 +136,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex _; } - /** - * @dev Throws if minimal tree update fee is not set. - */ - modifier minTreeUpdateFeeIsSet() { - require(minTreeUpdateFee > 0, "ZkBobPool: minimal tree update fee is not set"); - _; - } - /** * @dev Initializes pool proxy storage. * Callable only once and only through EIP1967Proxy constructor / upgradeToAndCall. @@ -278,7 +270,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex * Method uses a custom ABI encoding scheme described in CustomABIDecoder. * Single transact() call performs either deposit, withdrawal or shielded transfer operation. */ - function transactV2() external onlyOperator minTreeUpdateFeeIsSet { + function transactV2() external onlyOperator { require(_version() == 2, "ZkBobPool: incorrect calldata version"); address user = msg.sender; @@ -392,7 +384,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex ) external onlyOperator - minTreeUpdateFeeIsSet { (uint256 total, uint256 totalFee, uint256 hashsum, bytes memory message) = direct_deposit_queue.collect(_indices, _out_commit); From 8d193842f3836649ddea99afce13e6d34225cfcb Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Thu, 1 Feb 2024 15:08:46 +0300 Subject: [PATCH 26/40] Pack fee reciever and allowed in a single struct --- .../manager/AllowListOperatorManager.sol | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/zkbob/manager/AllowListOperatorManager.sol b/src/zkbob/manager/AllowListOperatorManager.sol index b5b4d01..de1b0fb 100644 --- a/src/zkbob/manager/AllowListOperatorManager.sol +++ b/src/zkbob/manager/AllowListOperatorManager.sol @@ -10,15 +10,22 @@ import {Ownable} from "../../utils/Ownable.sol"; * @dev Implements an allow list based access control for ZkBobPool relayers. */ contract AllowListOperatorManager is IOperatorManager, Ownable { + /** + * @param allowed flag to enable or disable operator. + * @param feeReceiver address of the fee receiver. + * @dev feeReceiver is still active even if allowed is false. + */ + struct Operator { + bool allowed; + address feeReceiver; + } + // if true, only whitelisted addresses can be operators // if false, anyone can be an operator bool public allowListEnabled; // mapping of whitelisted operator addresses - mapping(address => bool) public operators; - - // mapping of fee receivers for operators - mapping(address => address) public operatorFeeReceiver; + mapping(address => Operator) public operators; event UpdateOperator(address indexed operator, address feeReceiver, bool allowed); event UpdateAllowListEnabled(bool enabled); @@ -91,16 +98,16 @@ contract AllowListOperatorManager is IOperatorManager, Ownable { */ function setFeeReceiver(address _feeReceiver) external { require(isOperator(msg.sender), "OperatorManager: operator not allowed"); - operatorFeeReceiver[msg.sender] = _feeReceiver; + operators[msg.sender].feeReceiver = _feeReceiver; emit UpdateOperator(msg.sender, _feeReceiver, true); } function _setOperator(address _operator, bool _allowed, address _feeReceiver) internal nonZeroAddress(_operator) { - operators[_operator] = _allowed; + operators[_operator].allowed = _allowed; if (_allowed) { - operatorFeeReceiver[_operator] = _feeReceiver; + operators[_operator].feeReceiver = _feeReceiver; } - emit UpdateOperator(_operator, operatorFeeReceiver[_operator], _allowed); + emit UpdateOperator(_operator, operators[_operator].feeReceiver, _allowed); } /** @@ -108,7 +115,7 @@ contract AllowListOperatorManager is IOperatorManager, Ownable { * @param _addr address to check. */ function isOperator(address _addr) public view override returns (bool) { - return operators[_addr] || !allowListEnabled; + return operators[_addr].allowed || !allowListEnabled; } /** @@ -117,6 +124,6 @@ contract AllowListOperatorManager is IOperatorManager, Ownable { * @param _addr address to check. */ function isOperatorFeeReceiver(address _operator, address _addr) external view override returns (bool) { - return operatorFeeReceiver[_operator] == _addr; + return operators[_operator].feeReceiver == _addr; } } From d5648f73b2421878973388eea36374bdf7862453 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Fri, 2 Feb 2024 11:42:09 +0300 Subject: [PATCH 27/40] Remove lastTreeUpdateTimestamp --- src/zkbob/ZkBobPool.sol | 17 ++--------------- test/zkbob/ZkBobPoolDecentralized.t.sol | 6 +++--- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 07c6de4..707d42f 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -72,11 +72,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex */ Queue.CommitmentQueue internal pendingCommitments; - /** - * @dev Timestamp of the last tree update. - */ - uint64 public lastTreeUpdateTimestamp; - /** * @dev The duration of the grace period within which only the prover who submitted the transaction * can submit the tree update proof. @@ -190,8 +185,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex { PendingCommitment memory op = pendingCommitments.front(); require(op.commitment != 0, "ZkBobPool: no pending commitment"); - uint64 gracePeriodStart = op.timestamp > lastTreeUpdateTimestamp ? op.timestamp : lastTreeUpdateTimestamp; - gracePeriodEnd = gracePeriodStart + gracePeriod; + gracePeriodEnd = op.timestamp + gracePeriod; return (op.commitment, op.prover, op.fee, op.timestamp, gracePeriodEnd); } @@ -445,7 +439,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex pool_index += 128; roots[pool_index] = _rootAfter; accumulatedFee[msg.sender] += commitment.fee; - lastTreeUpdateTimestamp = uint64(block.timestamp); emit RootUpdated(pool_index, _rootAfter, _commitment); } @@ -505,14 +498,8 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex * is the prover who submitted this commitment. */ function _validateGracePeriod(uint64 commitmentTimestamp, address privilegedProver) internal view { - // We calculate the beggining of the grace period either from the timestamp of the last tree update, - // or from the timestamp of the commitment, whichever is greater. - uint64 timestamp = commitmentTimestamp; - if (timestamp < lastTreeUpdateTimestamp) { - timestamp = lastTreeUpdateTimestamp; - } require( - block.timestamp > timestamp + gracePeriod || msg.sender == privilegedProver, + block.timestamp > commitmentTimestamp + gracePeriod || msg.sender == privilegedProver, "ZkBobPool: prover is not allowed to submit the proof yet" ); } diff --git a/test/zkbob/ZkBobPoolDecentralized.t.sol b/test/zkbob/ZkBobPoolDecentralized.t.sol index 351e1d0..5f6845b 100644 --- a/test/zkbob/ZkBobPoolDecentralized.t.sol +++ b/test/zkbob/ZkBobPoolDecentralized.t.sol @@ -76,7 +76,7 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas _proveTreeUpdateExpectRevert(notAllowedProver, "ZkBobPool: not an operator"); } - function testGracePeriodStartsOnlyAfterTreeUpdatingIfNewTransactSendedBeforeOldTreeUpdate() public { + function testGracePeriodsMayIntersect() public { deal(token, user1, 100 ether / D); bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); @@ -88,8 +88,8 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas vm.warp(block.timestamp + pool.gracePeriod()); _proveTreeUpdate(prover1); - vm.warp(block.timestamp + pool.gracePeriod()); - _proveTreeUpdate(prover2); + vm.warp(block.timestamp + 1); + _proveTreeUpdate(prover1); } function testFeeDistribution() public { From 3d39ff4c1844f8d064ac073b1ee3801b5749bbb4 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Fri, 2 Feb 2024 12:13:41 +0300 Subject: [PATCH 28/40] Add gaps, fix testNativeWithdrawal --- src/zkbob/ZkBobPool.sol | 11 +++++------ test/zkbob/ZkBobPool.t.sol | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 707d42f..4024d15 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -61,11 +61,10 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex mapping(address => uint256) public accumulatedFee; - // TODO: - // Is it safe to just add deprecated gap here and - // call setTokenSeller() again after upgrade to fix storage layout? - // Additionally, it makes sense to add __gap as well. - // address private __deprecatedGap2; + /** + * @dev It is the slot where tokenSeller was stored. + */ + address private __deprecatedGap2; /** * @dev Queue of pending commitments to be included in the Merkle Tree. @@ -85,7 +84,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex */ uint64 public minTreeUpdateFee; - // uint256[50] __gap; + uint256[50] __gap; event UpdateOperatorManager(address manager); event UpdateAccounting(address accounting); diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index e4ae3f2..63238b7 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -889,7 +889,7 @@ abstract contract AbstractZkBobPoolTest is AbstractZkBobPoolTestBase { addr = address(new UniswapV3Seller(uniV3Router, uniV3Quoter, token, 100, tempToken, 500)); } pool.setTokenSeller(addr); - assertEq(address(uint160(uint256(vm.load(address(pool), bytes32(uint256(11)))))), addr); + assertEq(address(uint160(uint256(vm.load(address(pool), bytes32(uint256(66)))))), addr); } vm.deal(user1, 0); From 95ef57e357660030a384b21fb22124cd08058a89 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Fri, 2 Feb 2024 12:15:14 +0300 Subject: [PATCH 29/40] Fix formatting --- src/zkbob/ZkBobPool.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 4024d15..8b3bf07 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -62,8 +62,8 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex mapping(address => uint256) public accumulatedFee; /** - * @dev It is the slot where tokenSeller was stored. - */ + * @dev It is the slot where tokenSeller was stored. + */ address private __deprecatedGap2; /** From d640c763ab39dfb196a9745a9a5a2b43027961aa Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Thu, 22 Feb 2024 16:36:44 +0300 Subject: [PATCH 30/40] Update ZkBobPool.s.sol and Local.s.sol scripts --- script/scripts/Env.s.sol | 46 ++++++++++++++++++------ script/scripts/Local.s.sol | 53 +++++++++++++++++++++------ script/scripts/ZkBobPool.s.sol | 66 +++++++++++++++++++++++++++------- 3 files changed, 132 insertions(+), 33 deletions(-) diff --git a/script/scripts/Env.s.sol b/script/scripts/Env.s.sol index 22abec4..7e5be56 100644 --- a/script/scripts/Env.s.sol +++ b/script/scripts/Env.s.sol @@ -24,24 +24,48 @@ bytes32 constant bobSalt = bytes32(uint256(285834900769)); // zkbob uint256 constant zkBobPoolId = 0; // 0 is reserved for Polygon MVP pool, do not use for other deployments -PoolType constant zkBobPoolType = PoolType.BOB; -string constant zkBobVerifiers = "prodV1"; -address constant zkBobToken = 0xB0B195aEFA3650A6908f15CdaC7D92F8a5791B0B; +PoolType constant zkBobPoolType = PoolType.USDC; +string constant zkBobVerifiers = "prodV2"; +address constant zkBobToken = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC uint256 constant zkBobInitialRoot = 11469701942666298368112882412133877458305516134926649826543144744382391691533; address constant zkBobRelayer = 0xc2c4AD59B78F4A0aFD0CDB8133E640Db08Fa5b90; address constant zkBobRelayerFeeReceiver = 0x758768EC473279c4B1Aa61FA5450745340D4B17d; -string constant zkBobRelayerURL = "https://relayer-mvp.zkbob.com"; -uint256 constant zkBobPoolCap = 1_000_000 ether; -uint256 constant zkBobDailyDepositCap = 100_000 ether; -uint256 constant zkBobDailyWithdrawalCap = 100_000 ether; -uint256 constant zkBobDailyUserDepositCap = 10_000 ether; -uint256 constant zkBobDepositCap = 10_000 ether; -uint256 constant zkBobDailyUserDirectDepositCap = 0; -uint256 constant zkBobDirectDepositCap = 0; uint256 constant zkBobDirectDepositFee = 0.1 gwei; uint256 constant zkBobDirectDepositTimeout = 1 days; address constant permit2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; +// decentralized +uint64 constant gracePeriod = 10 minutes; // TODO +uint64 constant minTreeUpdateFee = 0.1 gwei; +bool constant allowListEnabled = true; + +// accounting +address constant kycManager = address(0); + +uint256 constant tier0TvlCap = 2_000_000 gwei; +uint256 constant tier0DailyDepositCap = 300_000 gwei; +uint256 constant tier0DailyWithdrawalCap = 300_000 gwei; +uint256 constant tier0DailyUserDepositCap = 10_000 gwei; +uint256 constant tier0DepositCap = 10_000 gwei; +uint256 constant tier0DailyUserDirectDepositCap = 10_000 gwei; +uint256 constant tier0DirectDepositCap = 1_000 gwei; + +uint256 constant tier1TvlCap = 2_000_000 gwei; +uint256 constant tier1DailyDepositCap = 300_000 gwei; +uint256 constant tier1DailyWithdrawalCap = 300_000 gwei; +uint256 constant tier1DailyUserDepositCap = 100_000 gwei; +uint256 constant tier1DepositCap = 100_000 gwei; +uint256 constant tier1DailyUserDirectDepositCap = 10_000 gwei; +uint256 constant tier1DirectDepositCap = 1_000 gwei; + +uint256 constant tier254TvlCap = 2_000_000 gwei; +uint256 constant tier254DailyDepositCap = 300_000 gwei; +uint256 constant tier254DailyWithdrawalCap = 300_000 gwei; +uint256 constant tier254DailyUserDepositCap = 20_000 gwei; +uint256 constant tier254DepositCap = 20_000 gwei; +uint256 constant tier254DailyUserDirectDepositCap = 10_000 gwei; +uint256 constant tier254DirectDepositCap = 1_000 gwei; + // new zkbob impl address constant zkBobPool = 0x72e6B59D4a90ab232e55D4BB7ed2dD17494D62fB; diff --git a/script/scripts/Local.s.sol b/script/scripts/Local.s.sol index eac51bc..9ea08e7 100644 --- a/script/scripts/Local.s.sol +++ b/script/scripts/Local.s.sol @@ -8,7 +8,7 @@ import "../../test/shared/EIP2470.t.sol"; import "../../src/BobToken.sol"; import "../../src/proxy/EIP1967Proxy.sol"; import "../../src/zkbob/ZkBobPoolBOB.sol"; -import "../../src/zkbob/manager/MutableOperatorManager.sol"; +import {AllowListOperatorManager} from "../../src/zkbob/manager/AllowListOperatorManager.sol"; import "../../src/zkbob/ZkBobDirectDepositQueue.sol"; import "../../src/zkbob/utils/ZkBobAccounting.sol"; @@ -59,28 +59,61 @@ contract DeployLocal is Script { { ZkBobAccounting accounting = new ZkBobAccounting(address(pool), 1_000_000_000); + if (kycManager != address(0)) { + accounting.setKycProvidersManager(IKycProvidersManager(kycManager)); + } accounting.setLimits( 0, - zkBobPoolCap, - zkBobDailyDepositCap, - zkBobDailyWithdrawalCap, - zkBobDailyUserDepositCap, - zkBobDepositCap, - zkBobDailyUserDirectDepositCap, - zkBobDirectDepositCap + tier0TvlCap, + tier0DailyDepositCap, + tier0DailyWithdrawalCap, + tier0DailyUserDepositCap, + tier0DepositCap, + tier0DailyUserDirectDepositCap, + tier0DirectDepositCap + ); + accounting.setLimits( + 1, + tier1TvlCap, + tier1DailyDepositCap, + tier1DailyWithdrawalCap, + tier1DailyUserDepositCap, + tier1DepositCap, + tier1DailyUserDirectDepositCap, + tier1DirectDepositCap + ); + accounting.setLimits( + 254, + tier254TvlCap, + tier254DailyDepositCap, + tier254DailyWithdrawalCap, + tier254DailyUserDepositCap, + tier254DepositCap, + tier254DailyUserDirectDepositCap, + tier254DirectDepositCap ); pool.setAccounting(accounting); } { - IOperatorManager operatorManager = - new MutableOperatorManager(zkBobRelayer, zkBobRelayerFeeReceiver, zkBobRelayerURL); + address[] memory operators = new address[](1); + operators[0] = zkBobRelayer; + + address[] memory feeReceivers = new address[](1); + feeReceivers[0] = zkBobRelayerFeeReceiver; + + IOperatorManager operatorManager = new AllowListOperatorManager(operators, feeReceivers, allowListEnabled); pool.setOperatorManager(operatorManager); queue.setOperatorManager(operatorManager); queue.setDirectDepositFee(uint64(zkBobDirectDepositFee)); queue.setDirectDepositTimeout(uint40(zkBobDirectDepositTimeout)); } + { + pool.setGracePeriod(gracePeriod); + pool.setMinTreeUpdateFee(minTreeUpdateFee); + } + { if (owner != address(0)) { bob.transferOwnership(owner); diff --git a/script/scripts/ZkBobPool.s.sol b/script/scripts/ZkBobPool.s.sol index 037ea91..5ad5f14 100644 --- a/script/scripts/ZkBobPool.s.sol +++ b/script/scripts/ZkBobPool.s.sol @@ -7,7 +7,7 @@ import "./Env.s.sol"; import "../../src/proxy/EIP1967Proxy.sol"; import "../../src/zkbob/ZkBobDirectDepositQueue.sol"; import "../../src/zkbob/ZkBobDirectDepositQueueETH.sol"; -import "../../src/zkbob/manager/MutableOperatorManager.sol"; +import {AllowListOperatorManager} from "../../src/zkbob/manager/AllowListOperatorManager.sol"; import "../../src/zkbob/ZkBobPoolBOB.sol"; import "../../src/zkbob/ZkBobPoolETH.sol"; import "../../src/zkbob/ZkBobPoolUSDC.sol"; @@ -97,29 +97,65 @@ contract DeployZkBobPool is Script { vars.queueProxy.upgradeTo(address(queueImpl)); ZkBobDirectDepositQueue queue = ZkBobDirectDepositQueue(address(vars.queueProxy)); - IOperatorManager operatorManager = - new MutableOperatorManager(zkBobRelayer, zkBobRelayerFeeReceiver, zkBobRelayerURL); - pool.setOperatorManager(operatorManager); - queue.setOperatorManager(operatorManager); + AllowListOperatorManager operatorManager; + { + address[] memory operators = new address[](1); + operators[0] = zkBobRelayer; + + address[] memory feeReceivers = new address[](1); + feeReceivers[0] = zkBobRelayerFeeReceiver; + + operatorManager = new AllowListOperatorManager(operators, feeReceivers, allowListEnabled); + pool.setOperatorManager(operatorManager); + queue.setOperatorManager(operatorManager); + } + queue.setDirectDepositFee(uint64(zkBobDirectDepositFee)); queue.setDirectDepositTimeout(uint40(zkBobDirectDepositTimeout)); ZkBobAccounting accounting = new ZkBobAccounting(address(pool), vars.precision); + if (kycManager != address(0)) { + accounting.setKycProvidersManager(IKycProvidersManager(kycManager)); + } accounting.setLimits( 0, - zkBobPoolCap, - zkBobDailyDepositCap, - zkBobDailyWithdrawalCap, - zkBobDailyUserDepositCap, - zkBobDepositCap, - zkBobDailyUserDirectDepositCap, - zkBobDirectDepositCap + tier0TvlCap, + tier0DailyDepositCap, + tier0DailyWithdrawalCap, + tier0DailyUserDepositCap, + tier0DepositCap, + tier0DailyUserDirectDepositCap, + tier0DirectDepositCap + ); + accounting.setLimits( + 1, + tier1TvlCap, + tier1DailyDepositCap, + tier1DailyWithdrawalCap, + tier1DailyUserDepositCap, + tier1DepositCap, + tier1DailyUserDirectDepositCap, + tier1DirectDepositCap + ); + accounting.setLimits( + 254, + tier254TvlCap, + tier254DailyDepositCap, + tier254DailyWithdrawalCap, + tier254DailyUserDepositCap, + tier254DepositCap, + tier254DailyUserDirectDepositCap, + tier254DirectDepositCap ); pool.setAccounting(accounting); + pool.setGracePeriod(gracePeriod); + pool.setMinTreeUpdateFee(minTreeUpdateFee); + if (owner != address(0)) { pool.transferOwnership(owner); queue.transferOwnership(owner); + operatorManager.transferOwnership(owner); } if (admin != tx.origin) { @@ -138,12 +174,18 @@ contract DeployZkBobPool is Script { require(pool.transfer_verifier() == transferVerifier, "Transfer verifier is not configured"); require(pool.tree_verifier() == treeVerifier, "Tree verifier is not configured"); require(pool.batch_deposit_verifier() == batchDepositVerifier, "Batch deposit verifier is not configured"); + require(pool.gracePeriod() == gracePeriod, "Grace period is not configured"); + require(pool.minTreeUpdateFee() == minTreeUpdateFee, "Min tree update fee is not configured"); + require(address(pool.accounting()) == address(accounting), "Accounting is not configured"); + require(address(pool.operatorManager()) == address(operatorManager), "Operator manager is not configured"); + require(address(queue.operatorManager()) == address(operatorManager), "Operator manager is not configured"); console2.log("ZkBobPool:", address(pool)); console2.log("ZkBobPool implementation:", address(vars.poolImpl)); console2.log("ZkBobDirectDepositQueue:", address(queue)); console2.log("ZkBobDirectDepositQueue implementation:", address(queueImpl)); console2.log("ZkBobAccounting:", address(accounting)); + console2.log("AllowListOperatorManager:", address(operatorManager)); console2.log("TransferVerifier:", address(transferVerifier)); console2.log("TreeUpdateVerifier:", address(treeVerifier)); console2.log("BatchDepositVierifier:", address(batchDepositVerifier)); From e3da1836550629d6ecb99af7c61370c0cb82ff3d Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Thu, 22 Feb 2024 19:10:50 +0300 Subject: [PATCH 31/40] Add script to deploy new operator, modify upgrade script --- .../DeployAllowListOperatorManager.s.sol | 26 +++++ script/scripts/DeployZkBobPoolModules.s.sol | 24 ++++- test/scripts/DeployZkBobPoolModules.t.sol | 98 +++++++++++++++++++ 3 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 script/scripts/DeployAllowListOperatorManager.s.sol create mode 100644 test/scripts/DeployZkBobPoolModules.t.sol diff --git a/script/scripts/DeployAllowListOperatorManager.s.sol b/script/scripts/DeployAllowListOperatorManager.s.sol new file mode 100644 index 0000000..2114b1b --- /dev/null +++ b/script/scripts/DeployAllowListOperatorManager.s.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.15; + +import "forge-std/Script.sol"; +import "./Env.s.sol"; +import {AllowListOperatorManager} from "../../src/zkbob/manager/AllowListOperatorManager.sol"; + +contract DeployAllowListOperatorManager is Script { + function run() external { + vm.startBroadcast(); + + address[] memory operators = new address[](1); + operators[0] = zkBobRelayer; + + address[] memory feeReceivers = new address[](1); + feeReceivers[0] = zkBobRelayerFeeReceiver; + + AllowListOperatorManager operatorManager = + new AllowListOperatorManager(operators, feeReceivers, allowListEnabled); + + vm.stopBroadcast(); + + console2.log("AllowListOperatorManager address:", address(operatorManager)); + } +} diff --git a/script/scripts/DeployZkBobPoolModules.s.sol b/script/scripts/DeployZkBobPoolModules.s.sol index 6914fa0..7bed35a 100644 --- a/script/scripts/DeployZkBobPoolModules.s.sol +++ b/script/scripts/DeployZkBobPoolModules.s.sol @@ -18,7 +18,7 @@ contract DummyDelegateCall { } contract Migrator { - function migrate(address _target, address _newImpl, address _accounting) external { + function migrate(address _target, address _newImpl, address _accounting, address _tokenSeller) external { address kycManager = address(ZkBobAccounting(_target).kycProvidersManager()); EIP1967Proxy(payable(_target)).upgradeTo(_newImpl); @@ -32,6 +32,11 @@ contract Migrator { ZkBobPool(_target).initializePoolIndex(txCount * 128); ZkBobPool(_target).setAccounting(IZkBobAccounting(_accounting)); + ZkBobPool(_target).setGracePeriod(gracePeriod); + ZkBobPool(_target).setMinTreeUpdateFee(minTreeUpdateFee); + ZkBobPoolUSDC(_target).setTokenSeller(_tokenSeller); + + // TODO: why are we using txCount + 1? ZkBobAccounting(_accounting).initialize(txCount + 1, tvl, cumTvl, maxWeeklyTxCount, maxWeeklyAvgTvl); ZkBobAccounting(_accounting).setKycProvidersManager(IKycProvidersManager(kycManager)); ZkBobAccounting(_accounting).setLimits( @@ -54,14 +59,20 @@ contract Migrator { contract DeployZkBobPoolModules is Script, Test { function run() external { - ZkBobPoolUSDC pool = ZkBobPoolUSDC(address(zkBobPool)); + runWithPoolAddress(address(zkBobPool), true); + } + + function runWithPoolAddress(address poolAddress, bool broadcast) public { + ZkBobPoolUSDC pool = ZkBobPoolUSDC(poolAddress); address owner = pool.owner(); vm.etch(owner, type(DummyDelegateCall).runtimeCode); address tokenSeller = address(pool.tokenSeller()); uint256 poolIndex = uint256(pool.pool_index()); - vm.startBroadcast(); + if (broadcast) { + vm.startBroadcast(); + } ZkBobPoolUSDC impl = new ZkBobPoolUSDC( pool.pool_id(), @@ -75,10 +86,13 @@ contract DeployZkBobPoolModules is Script, Test { ZkBobAccounting acc = new ZkBobAccounting(address(pool), 1_000_000_000); acc.transferOwnership(owner); DummyDelegateCall(owner).delegate( - address(mig), abi.encodeWithSelector(Migrator.migrate.selector, address(pool), address(impl), address(acc)) + address(mig), + abi.encodeWithSelector(Migrator.migrate.selector, address(pool), address(impl), address(acc), tokenSeller) ); - vm.stopBroadcast(); + if (broadcast) { + vm.stopBroadcast(); + } acc.slot0(); acc.slot1(); diff --git a/test/scripts/DeployZkBobPoolModules.t.sol b/test/scripts/DeployZkBobPoolModules.t.sol new file mode 100644 index 0000000..a83a251 --- /dev/null +++ b/test/scripts/DeployZkBobPoolModules.t.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: CC0-1.0 + +import {Test, console} from "forge-std/Test.sol"; +import {AbstractOptimismForkTest} from "../shared/ForkTests.t.sol"; +import {ZkBobPoolUSDC} from "../../src/zkbob/ZkBobPoolUSDC.sol"; +import {DeployZkBobPoolModules} from "../../script/scripts/DeployZkBobPoolModules.s.sol"; +import {EIP1967Proxy} from "../../src/proxy/EIP1967Proxy.sol"; +import {ZkBobAccounting} from "../../src/zkbob/utils/ZkBobAccounting.sol"; + +contract DeployZkBobPoolModulesTest is AbstractOptimismForkTest { + struct PoolStateCheck { + address owner; + bytes32 slot0; + bytes32 slot1; + address operatorManager; + uint256 poolIndex; + uint256 oneNullifier; + uint256 lastRoot; + bytes32 all_messages_hash; + uint256 relayerFee; + address tokenSeller; + } + + function testOptimismUSDCPoolUpgrade() public { + vm.createSelectFork(forkRpcUrl, 116204904); + ZkBobPoolUSDC pool = ZkBobPoolUSDC(0x1CA8C2B9B20E18e86d5b9a72370fC6c91814c97C); + address relayer = 0xb9CD01c0b417b4e9095f620aE2f849A84a9B1690; + + DeployZkBobPoolModules upgrade = new DeployZkBobPoolModules(); + + // It seems that DeployZkBobPoolModules assumes that proxyAdmin is the owner of the pool + // TODO: confirm it + address proxyAdmin = EIP1967Proxy(payable(address(pool))).admin(); + address owner = pool.owner(); + vm.prank(owner); + pool.transferOwnership(address(proxyAdmin)); + + // stack to deep + PoolStateCheck memory poolState = PoolStateCheck({ + owner: pool.owner(), + slot0: vm.load(address(pool), bytes32(uint256(1))), + slot1: vm.load(address(pool), bytes32(uint256(2))), + operatorManager: address(pool.operatorManager()), + poolIndex: pool.pool_index(), + oneNullifier: pool.nullifiers(0x39a833a5c374a0a3328f65ae9a9bf883945694cca613a8415c3a555bda388cd), + lastRoot: pool.roots(pool.pool_index()), + all_messages_hash: pool.all_messages_hash(), + relayerFee: pool.accumulatedFee(relayer), + tokenSeller: address(pool.tokenSeller()) + }); + + startHoax(proxyAdmin); + upgrade.runWithPoolAddress(address(pool), false); + vm.stopPrank(); + + assertEq(poolState.owner, pool.owner()); + assertEq(address(pool.redeemer()), address(0)); // TODO: energy redeemer will not be set by script + assertNotEq(address(pool.accounting()), address(0)); + assertEq(poolState.poolIndex, uint256(pool.pool_index())); + assertEq(poolState.operatorManager, address(pool.operatorManager())); + assertEq( + poolState.oneNullifier, pool.nullifiers(0x39a833a5c374a0a3328f65ae9a9bf883945694cca613a8415c3a555bda388cd) + ); + assertEq(poolState.lastRoot, pool.roots(pool.pool_index())); + assertEq(poolState.all_messages_hash, pool.all_messages_hash()); + assertEq(poolState.relayerFee, pool.accumulatedFee(relayer)); + assertEq(10 minutes, pool.gracePeriod()); + assertEq(0.1 gwei, pool.minTreeUpdateFee()); + assertEq(poolState.tokenSeller, address(pool.tokenSeller())); + + checkSlot0(uint256(poolState.slot0), ZkBobAccounting(address(pool.accounting()))); + checkSlot1(uint256(poolState.slot1), ZkBobAccounting(address(pool.accounting()))); + } + + function checkSlot0(uint256 slot0, ZkBobAccounting accounting) internal { + ( + uint56 maxWeeklyAvgTvl, + uint32 maxWeeklyTxCount, + uint24 tailSlot, + uint24 headSlot, + uint88 cumTvl, + uint32 txCount + ) = accounting.slot0(); + uint24 curSlot = uint24(block.timestamp / 1 hours); + + assertEq(uint56(slot0), maxWeeklyAvgTvl); + assertEq(uint32(slot0 >> 56), maxWeeklyTxCount); + assertEq(curSlot, tailSlot); + assertEq(curSlot, headSlot); + assertEq(uint88(slot0 >> (56 + 32 + 24 + 24)), cumTvl); + assertEq(uint32(slot0 >> (56 + 32 + 24 + 24 + 88)), txCount); + } + + function checkSlot1(uint256 slot1, ZkBobAccounting accounting) internal { + (uint72 tvl) = accounting.slot1(); + assertEq(uint72(slot1), tvl); + } +} From 0333691d55ee59787060d13a69b083dd316dfa9e Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Mon, 26 Feb 2024 15:05:18 +0300 Subject: [PATCH 32/40] Minor fixes --- script/scripts/DeployZkBobPoolModules.s.sol | 7 +++++-- test/scripts/DeployZkBobPoolModules.t.sol | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/script/scripts/DeployZkBobPoolModules.s.sol b/script/scripts/DeployZkBobPoolModules.s.sol index 7bed35a..7ccb5ad 100644 --- a/script/scripts/DeployZkBobPoolModules.s.sol +++ b/script/scripts/DeployZkBobPoolModules.s.sol @@ -36,8 +36,7 @@ contract Migrator { ZkBobPool(_target).setMinTreeUpdateFee(minTreeUpdateFee); ZkBobPoolUSDC(_target).setTokenSeller(_tokenSeller); - // TODO: why are we using txCount + 1? - ZkBobAccounting(_accounting).initialize(txCount + 1, tvl, cumTvl, maxWeeklyTxCount, maxWeeklyAvgTvl); + ZkBobAccounting(_accounting).initialize(txCount, tvl, cumTvl, maxWeeklyTxCount, maxWeeklyAvgTvl); ZkBobAccounting(_accounting).setKycProvidersManager(IKycProvidersManager(kycManager)); ZkBobAccounting(_accounting).setLimits( 0, 2_000_000 gwei, 300_000 gwei, 300_000 gwei, 10_000 gwei, 10_000 gwei, 10_000 gwei, 1_000 gwei @@ -57,6 +56,10 @@ contract Migrator { } } +/** + * @dev This script assumes that pool.owner == proxyAdmin + * @dev This script uses gracePeriod and minTreeUpdateFee from Env.s.sol + */ contract DeployZkBobPoolModules is Script, Test { function run() external { runWithPoolAddress(address(zkBobPool), true); diff --git a/test/scripts/DeployZkBobPoolModules.t.sol b/test/scripts/DeployZkBobPoolModules.t.sol index a83a251..2a69f2c 100644 --- a/test/scripts/DeployZkBobPoolModules.t.sol +++ b/test/scripts/DeployZkBobPoolModules.t.sol @@ -28,8 +28,7 @@ contract DeployZkBobPoolModulesTest is AbstractOptimismForkTest { DeployZkBobPoolModules upgrade = new DeployZkBobPoolModules(); - // It seems that DeployZkBobPoolModules assumes that proxyAdmin is the owner of the pool - // TODO: confirm it + // DeployZkBobPoolModules assumes that proxyAdmin is the owner of the pool address proxyAdmin = EIP1967Proxy(payable(address(pool))).admin(); address owner = pool.owner(); vm.prank(owner); @@ -54,7 +53,7 @@ contract DeployZkBobPoolModulesTest is AbstractOptimismForkTest { vm.stopPrank(); assertEq(poolState.owner, pool.owner()); - assertEq(address(pool.redeemer()), address(0)); // TODO: energy redeemer will not be set by script + assertEq(address(pool.redeemer()), address(0)); // redeemer is not set by script assertNotEq(address(pool.accounting()), address(0)); assertEq(poolState.poolIndex, uint256(pool.pool_index())); assertEq(poolState.operatorManager, address(pool.operatorManager())); @@ -70,6 +69,9 @@ contract DeployZkBobPoolModulesTest is AbstractOptimismForkTest { checkSlot0(uint256(poolState.slot0), ZkBobAccounting(address(pool.accounting()))); checkSlot1(uint256(poolState.slot1), ZkBobAccounting(address(pool.accounting()))); + + vm.expectRevert("ZkBobPool: queue is empty"); + pool.pendingCommitment(); } function checkSlot0(uint256 slot0, ZkBobAccounting accounting) internal { From ccc7c734d7bae03334914d8e43bb4ae77429c2fb Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Tue, 26 Mar 2024 17:05:24 +0300 Subject: [PATCH 33/40] Add privileged prover in calldata --- src/zkbob/ZkBobPool.sol | 15 +-- src/zkbob/utils/CustomABIDecoder.sol | 27 +++-- test/interfaces/IZkBobPoolAdmin.sol | 3 +- test/shared/Env.t.sol | 6 +- test/zkbob/ZkBobPool.t.sol | 67 +++++++------ test/zkbob/ZkBobPoolDecentralized.t.sol | 126 ++++++++++++++---------- 6 files changed, 143 insertions(+), 101 deletions(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 8b3bf07..7055b6a 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -290,12 +290,12 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex uint256 nullifier = _transfer_nullifier(); { - require(msg.sender == _memo_prover_address(), "ZkBobPool: unauthorized"); + require(msg.sender == _memo_proxy_address(), "ZkBobPool: unauthorized"); 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"); - _appendCommitment(_transfer_out_commit(), uint64(_memo_tree_update_fee()), msg.sender); + _appendCommitment(_transfer_out_commit(), uint64(_memo_tree_update_fee()), _memo_prover_address()); nullifiers[nullifier] = uint256(keccak256(abi.encodePacked(_transfer_out_commit(), _transfer_delta()))); @@ -373,7 +373,8 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex function appendDirectDeposits( uint256[] calldata _indices, uint256 _out_commit, - uint256[8] memory _batch_deposit_proof + uint256[8] memory _batch_deposit_proof, + address _prover ) external onlyOperator @@ -395,7 +396,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex require(totalFee >= minTreeUpdateFee, "ZkBobPool: tree update fee is too low"); uint64 ddFee = uint64(totalFee) - minTreeUpdateFee; - _appendCommitment(_out_commit, minTreeUpdateFee, msg.sender); + _appendCommitment(_out_commit, minTreeUpdateFee, _prover); bytes32 message_hash = keccak256(message); bytes32 _all_messages_hash = keccak256(abi.encodePacked(all_messages_hash, message_hash)); @@ -493,12 +494,12 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex } /** - * @dev Validates either the grace period has passed or the caller - * is the prover who submitted this commitment. + * @dev Validates that the prover is allowed to submit the tree update proof now. */ function _validateGracePeriod(uint64 commitmentTimestamp, address privilegedProver) internal view { require( - block.timestamp > commitmentTimestamp + gracePeriod || msg.sender == privilegedProver, + msg.sender == privilegedProver || privilegedProver == address(0) + || block.timestamp > commitmentTimestamp + gracePeriod, "ZkBobPool: prover is not allowed to submit the proof yet" ); } diff --git a/src/zkbob/utils/CustomABIDecoder.sol b/src/zkbob/utils/CustomABIDecoder.sol index cca968c..735c680 100644 --- a/src/zkbob/utils/CustomABIDecoder.sol +++ b/src/zkbob/utils/CustomABIDecoder.sol @@ -115,17 +115,17 @@ contract CustomABIDecoder { function _memo_fixed_size() internal pure returns (uint256 r) { uint256 t = _tx_type(); if (t == 0 || t == 1) { - // prover address + transact fee + tree update fee - // 20 + 8 + 8 - r = 36; + // proxy address + prover address + transact fee + tree update fee + // 20 + 20 + 8 + 8 + r = 56; } else if (t == 2) { - // prover address + transact fee + tree update fee + native amount + recipient - // 20 + 8 + 8 + 8 + 20 - r = 64; + // proxy address + prover address + transact fee + tree update fee + native amount + recipient + // 20 + 20 + 8 + 8 + 8 + 20 + r = 84; } else if (t == 3) { - // prover address + transact fee + tree update fee + deadline + address - // 20 + 8 + 8 + 8 + 20 - r = 64; + // proxy address + prover address + transact fee + tree update fee + deadline + address + // 20 + 20 + 8 + 8 + 8 + 20 + r = 84; } else { revert(); } @@ -145,7 +145,14 @@ contract CustomABIDecoder { } } - uint256 constant memo_prover_address_pos = memo_data_pos; + uint256 constant memo_proxy_address_pos = memo_data_pos; + uint256 constant memo_proxy_address_size = 20; + + function _memo_proxy_address() internal pure returns (address r) { + r = address(uint160(_loaduint256(memo_proxy_address_pos + memo_proxy_address_size - uint256_size))); + } + + uint256 constant memo_prover_address_pos = memo_proxy_address_pos + memo_proxy_address_size; uint256 constant memo_prover_address_size = 20; function _memo_prover_address() internal pure returns (address r) { diff --git a/test/interfaces/IZkBobPoolAdmin.sol b/test/interfaces/IZkBobPoolAdmin.sol index 813a1df..cf5989b 100644 --- a/test/interfaces/IZkBobPoolAdmin.sol +++ b/test/interfaces/IZkBobPoolAdmin.sol @@ -38,7 +38,8 @@ interface IZkBobPoolAdmin { function appendDirectDeposits( uint256[] calldata _indices, uint256 _out_commit, - uint256[8] memory _batch_deposit_proof + uint256[8] memory _batch_deposit_proof, + address _prover ) external; diff --git a/test/shared/Env.t.sol b/test/shared/Env.t.sol index 3a3e196..1d1cbef 100644 --- a/test/shared/Env.t.sol +++ b/test/shared/Env.t.sol @@ -17,8 +17,8 @@ address constant bobVanityAddr = address(0xB0B195aEFA3650A6908f15CdaC7D92F8a5791 bytes32 constant bobSalt = bytes32(uint256(285834900769)); uint256 constant forkBlockMainnet = 16200000; -string constant forkRpcUrlMainnet = "https://rpc.ankr.com/eth"; +string constant forkRpcUrlMainnet = "https://eth.llamarpc.com"; uint256 constant forkBlockPolygon = 37000000; -string constant forkRpcUrlPolygon = "https://rpc.ankr.com/polygon"; +string constant forkRpcUrlPolygon = "https://polygon.llamarpc.com"; uint256 constant forkBlockOptimism = 52000000; -string constant forkRpcUrlOptimism = "https://1rpc.io/op"; +string constant forkRpcUrlOptimism = "https://optimism.llamarpc.com"; diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 63238b7..927c2c2 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -206,7 +206,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { int256 _amount, uint256 _transactFee, uint256 _treeUpdateFee, - address prover + address _proxyAndProver ) internal view @@ -234,8 +234,9 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data = abi.encodePacked( data, uint16(0), - uint16(84), - prover, + uint16(104), + _proxyAndProver, + _proxyAndProver, uint64(_transactFee / denominator), uint64(_treeUpdateFee / denominator), _memoMessageAndExtraData(bytes2(0)) @@ -248,7 +249,7 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { uint256 _amount, uint256 _nativeAmount, uint256 _energyAmount, - address prover + address _proxyAndProver ) internal view @@ -267,11 +268,12 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { data = abi.encodePacked(data, _randFR()); } - data = abi.encodePacked(data, uint16(2), uint16(112)); + data = abi.encodePacked(data, uint16(2), uint16(132)); return abi.encodePacked( data, - prover, + _proxyAndProver, + _proxyAndProver, uint64(0.005 ether / D / denominator), uint64(0.005 ether / D / denominator), uint64(_nativeAmount / denominator), @@ -283,19 +285,19 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { function _encodeTransfer( uint256 _transactFee, uint256 _treeUpdateFee, - address _prover + address _proxyAndProver ) internal view returns (bytes memory) { - return _encodeTransferWithPrefix(_transactFee, _treeUpdateFee, _prover, bytes2(0)); + return _encodeTransferWithPrefix(_transactFee, _treeUpdateFee, _proxyAndProver, bytes2(0)); } function _encodeTransferWithPrefix( uint256 _transactFee, uint256 _treeUpdateFee, - address _prover, + address _proxyAndProver, bytes2 _prefix ) internal @@ -317,8 +319,9 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { return abi.encodePacked( data, uint16(1), - uint16(84), - _prover, + uint16(104), + _proxyAndProver, + _proxyAndProver, uint64(_transactFee / denominator), uint64(_treeUpdateFee / denominator), _memoMessageAndExtraData(_prefix) @@ -368,7 +371,20 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { int256 _amount, uint256 _transactFee, uint256 _treeUpdateFee, - address prover + address _proxyAndProver + ) + internal + returns (bytes memory) + { + return _encodePermitDeposit(_amount, _transactFee, _treeUpdateFee, _proxyAndProver, _proxyAndProver); + } + + function _encodePermitDeposit( + int256 _amount, + uint256 _transactFee, + uint256 _treeUpdateFee, + address _proxy, + address _prover ) internal returns (bytes memory) @@ -413,24 +429,19 @@ abstract contract AbstractZkBobPoolTestBase is AbstractForkTest { signature = abi.encodePacked(r, uint256(s) + (v == 28 ? (1 << 255) : 0)); } - bytes memory data = abi.encodePacked( - ZkBobPool.transactV2.selector, - uint8(2), - nullifier, - _randFR(), - uint48(0), - uint112(0), - int64(_amount / int256(denominator)) - ); + bytes memory data = abi.encodePacked(ZkBobPool.transactV2.selector, uint8(2)); + + data = abi.encodePacked(data, nullifier, _randFR(), uint48(0), uint112(0), int64(_amount / int256(denominator))); for (uint256 i = 0; i < 8; i++) { data = abi.encodePacked(data, _randFR()); } - data = abi.encodePacked(data, uint16(3), uint16(112)); + data = abi.encodePacked(data, uint16(3), uint16(132)); data = abi.encodePacked( data, - prover, + _proxy, + _prover, uint64(_transactFee / denominator), uint64(_treeUpdateFee / denominator), uint64(expiry), @@ -791,7 +802,7 @@ abstract contract AbstractZkBobPoolTest is AbstractZkBobPoolTestBase { vm.expectEmit(true, false, false, true); emit Message(128, bytes32(0), message); vm.prank(user2); - pool.appendDirectDeposits(indices, outCommitment, _randProof()); + pool.appendDirectDeposits(indices, outCommitment, _randProof(), address(0)); } function testRefundDirectDeposit() public { @@ -1004,7 +1015,7 @@ abstract contract AbstractZkBobPoolTest is AbstractZkBobPoolTestBase { function testTransactMessageEvent() public { bytes memory data = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, user2); - bytes memory message = _slice(data, 423, 36); + bytes memory message = _slice(data, 443, 36); vm.expectEmit(true, false, false, true); emit Message(128, bytes32(0), message); _transact(data); @@ -1014,21 +1025,21 @@ abstract contract AbstractZkBobPoolTest is AbstractZkBobPoolTestBase { IERC20(token).approve(address(pool), 0.11 ether / D); bytes memory data1 = _encodeDeposit(int256(0.1 ether / D), 0.005 ether / D, 0.005 ether / D, user2); - bytes memory message1 = _slice(data1, 395, 36); + bytes memory message1 = _slice(data1, 415, 36); vm.expectEmit(true, false, false, true); emit Message(256, bytes32(0), message1); _transact(data1); _proveTreeUpdate(); bytes memory data2 = _encodeTransfer(0.005 ether / D, 0.005 ether / D, user2); - bytes memory message2 = _slice(data2, 395, 36); + bytes memory message2 = _slice(data2, 415, 36); vm.expectEmit(true, false, false, true); emit Message(384, bytes32(0), message2); _transact(data2); _proveTreeUpdate(); bytes memory data3 = _encodeWithdrawal(user1, 0.1 ether / D, 0, 0, user2); - bytes memory message3 = _slice(data3, 423, 36); + bytes memory message3 = _slice(data3, 443, 36); vm.expectEmit(true, false, false, true); emit Message(512, bytes32(0), message3); _transact(data3); diff --git a/test/zkbob/ZkBobPoolDecentralized.t.sol b/test/zkbob/ZkBobPoolDecentralized.t.sol index 5f6845b..0801cbb 100644 --- a/test/zkbob/ZkBobPoolDecentralized.t.sol +++ b/test/zkbob/ZkBobPoolDecentralized.t.sol @@ -12,64 +12,80 @@ import "../shared/ForkTests.t.sol"; abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBase { AllowListOperatorManager manager; + address proxy1 = makeAddr("Proxy #1"); address prover1 = makeAddr("Prover #1"); address feeReceiver1 = makeAddr("Fee Receiver #1"); + address proxy2 = makeAddr("Proxy #2"); address prover2 = makeAddr("Prover #2"); address feeReceiver2 = makeAddr("Fee Receiver #2"); + address notAllowedProxy = makeAddr("Not Allowed Proxy"); address notAllowedProver = makeAddr("Not Allowed Prover"); - address[] provers = [prover1, prover2]; - address[] feeReceivers = [feeReceiver1, feeReceiver2]; + address[] operators = [prover1, prover2, proxy1, proxy2]; + address[] feeReceivers = [feeReceiver1, feeReceiver2, feeReceiver1, feeReceiver2]; function setUp() public override { super.setUp(); - manager = new AllowListOperatorManager(provers, feeReceivers, true); + manager = new AllowListOperatorManager(operators, feeReceivers, true); pool.setOperatorManager(IOperatorManager(manager)); } - function testOnlyAllowedProversCanTransact() public { + function testOnlyAllowedProxiesCanTransact() public { deal(token, user1, 100 ether / D); - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); - _transact(data1, prover1); + bytes memory data1 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy1, prover1); + _transact(data1, proxy1); - bytes memory data2 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover2); - _transact(data2, prover2); + bytes memory data2 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy2, prover2); + _transact(data2, proxy2); bytes memory data3 = - _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, notAllowedProver); - _transactExpectRevert(data3, notAllowedProver, "ZkBobPool: not an operator"); + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, notAllowedProxy); + _transactExpectRevert(data3, notAllowedProxy, "ZkBobPool: not an operator"); manager.setAllowListEnabled(false); - _transact(data3, notAllowedProver); + _transact(data3, notAllowedProxy); } function testOnlyPrivilegedProverCanUpdateTreeWithinGracePeriod() public { - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); - _transact(data1, prover1); + bytes memory data1 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy1, prover1); + _transact(data1, proxy1); vm.warp(block.timestamp + pool.gracePeriod()); + _proveTreeUpdateExpectRevert(proxy1, "ZkBobPool: prover is not allowed to submit the proof yet"); _proveTreeUpdateExpectRevert(prover2, "ZkBobPool: prover is not allowed to submit the proof yet"); _proveTreeUpdate(prover1); } function testAnyAllowedProverCanUpdateTreeAfterGracePeriod() public { - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); - _transact(data1, prover1); + bytes memory data1 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy1, prover1); + _transact(data1, proxy1); vm.warp(block.timestamp + pool.gracePeriod() + 1); _proveTreeUpdate(prover2); } + function testAnyAllowedProverCanUpdateTreeIfProverIsNotAssigned() public { + bytes memory data1 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy1, address(0)); + _transact(data1, proxy1); + _proveTreeUpdate(prover2); + } + function testNotAllowedProverCantUpdateTreeEvenAfterGracePeriod() public { - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); - _transact(data1, prover1); + bytes memory data1 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy1, address(0)); + _transact(data1, proxy1); vm.warp(block.timestamp + pool.gracePeriod() + 1); @@ -79,11 +95,13 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas function testGracePeriodsMayIntersect() public { deal(token, user1, 100 ether / D); - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); - _transact(data1, prover1); + bytes memory data1 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy1, prover1); + _transact(data1, proxy1); - bytes memory data2 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover2); - _transact(data2, prover2); + bytes memory data2 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy2, prover2); + _transact(data2, proxy2); vm.warp(block.timestamp + pool.gracePeriod()); _proveTreeUpdate(prover1); @@ -93,14 +111,13 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas } function testFeeDistribution() public { - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.017 ether / D, 0.005 ether / D, prover1); - _transact(data1, prover1); - assertEq(pool.accumulatedFee(prover1), 0.017 ether / (D * denominator)); + bytes memory data1 = + _encodePermitDeposit(int256(0.5 ether / D), 0.017 ether / D, 0.005 ether / D, proxy1, address(0)); + _transact(data1, proxy1); + assertEq(pool.accumulatedFee(proxy1), 0.017 ether / (D * denominator)); vm.prank(feeReceiver1); - pool.withdrawFee(prover1, feeReceiver1); - - vm.warp(block.timestamp + pool.gracePeriod() + 1); + pool.withdrawFee(proxy1, feeReceiver1); _proveTreeUpdate(prover2); assertEq(pool.accumulatedFee(prover2), 0.005 ether / (D * denominator)); @@ -118,8 +135,9 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas } function testCantSkipCommitments() public { - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); - _transact(data1, prover1); + bytes memory data1 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy1, prover1); + _transact(data1, proxy1); vm.expectRevert("ZkBobPool: commitment mismatch"); vm.prank(prover1); @@ -130,43 +148,46 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas deal(token, user1, 100 ether / D); pool.setMinTreeUpdateFee(uint64(0.01 ether / (D * denominator))); - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.009 ether / D, prover1); - _transactExpectRevert(data1, prover1, "ZkBobPool: tree update fee is too low"); + bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.009 ether / D, proxy1); + _transactExpectRevert(data1, proxy1, "ZkBobPool: tree update fee is too low"); - bytes memory data2 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.01 ether / D, prover1); - _transact(data2, prover1); + bytes memory data2 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.01 ether / D, proxy1); + _transact(data2, proxy1); - bytes memory data3 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.011 ether / D, prover1); - _transact(data3, prover1); + bytes memory data3 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.011 ether / D, proxy1); + _transact(data3, proxy1); } function testIndexInMessageEventIsConstructedCorrectly() public { uint256 index = pool.pool_index(); deal(token, user1, 100 ether / D); - bytes memory data1 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover1); + bytes memory data1 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy1, prover1); vm.expectEmit(true, false, false, false); bytes memory message = new bytes(0); emit Message(index + 128, bytes32(0), message); - _transact(data1, prover1); + _transact(data1, proxy1); - bytes memory data2 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover2); + bytes memory data2 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy2, prover2); vm.expectEmit(true, false, false, false); emit Message(index + 2 * 128, bytes32(0), message); - _transact(data2, prover2); + _transact(data2, proxy2); (uint256[] memory indices, uint256 commitment, uint256[8] memory proof) = _prepareRandomDirectDeposits(0); vm.expectEmit(true, false, false, false); emit Message(index + 3 * 128, bytes32(0), message); - _appendDirectDeposits(indices, commitment, proof, prover1); + _appendDirectDeposits(indices, commitment, proof, proxy1, prover1); _proveTreeUpdate(prover1); _proveTreeUpdate(prover2); - bytes memory data3 = _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, prover2); + bytes memory data3 = + _encodePermitDeposit(int256(0.5 ether / D), 0.005 ether / D, 0.005 ether / D, proxy2, prover2); vm.expectEmit(true, false, false, false); emit Message(index + 4 * 128, bytes32(0), message); - _transact(data3, prover2); + _transact(data3, proxy2); _proveTreeUpdate(prover1); _proveTreeUpdate(prover2); @@ -174,7 +195,7 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas (indices, commitment, proof) = _prepareRandomDirectDeposits(2); vm.expectEmit(true, false, false, false); emit Message(index + 5 * 128, bytes32(0), message); - _appendDirectDeposits(indices, commitment, proof, prover2); + _appendDirectDeposits(indices, commitment, proof, proxy2, prover2); _proveTreeUpdate(prover2); } @@ -196,9 +217,9 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas indices[1] = 1; uint256 outCommitment = _randFR(); - vm.prank(prover1); + vm.prank(proxy1); vm.expectRevert("ZkBobPool: tree update fee is too low"); - pool.appendDirectDeposits(indices, outCommitment, _randProof()); + pool.appendDirectDeposits(indices, outCommitment, _randProof(), prover1); } function testDirectDepositsTreeFeesAccrued() public { @@ -220,13 +241,13 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas indices[1] = 1; uint256 outCommitment = _randFR(); - vm.prank(prover1); - pool.appendDirectDeposits(indices, outCommitment, _randProof()); + vm.prank(proxy1); + pool.appendDirectDeposits(indices, outCommitment, _randProof(), prover1); uint64 expectedFee = uint64(singleDirectDepositFee * 2 - minTreeUpdateFee); - assertEq(expectedFee, pool.accumulatedFee(prover1)); + assertEq(expectedFee, pool.accumulatedFee(proxy1)); - vm.prank(prover1); - pool.withdrawFee(prover1, feeReceiver1); + vm.prank(proxy1); + pool.withdrawFee(proxy1, feeReceiver1); assertEq(IERC20(token).balanceOf(feeReceiver1), expectedFee * denominator); _proveTreeUpdate(prover1); @@ -290,12 +311,13 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas uint256[] memory indices, uint256 commitment, uint256[8] memory proof, + address proxy, address prover ) internal { - vm.prank(prover); - pool.appendDirectDeposits(indices, commitment, proof); + vm.prank(proxy); + pool.appendDirectDeposits(indices, commitment, proof, prover); } } From a41c6aaec0636bda94eade18430bd352a950d4ed Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Tue, 26 Mar 2024 18:11:02 +0300 Subject: [PATCH 34/40] Update scripts --- .../DeployAllowListOperatorManager.s.sol | 10 +++++---- script/scripts/Env.s.sol | 22 ++++++++++--------- script/scripts/Local.s.sol | 10 +++++---- script/scripts/ZkBobPool.s.sol | 10 +++++---- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/script/scripts/DeployAllowListOperatorManager.s.sol b/script/scripts/DeployAllowListOperatorManager.s.sol index 2114b1b..80a1f3e 100644 --- a/script/scripts/DeployAllowListOperatorManager.s.sol +++ b/script/scripts/DeployAllowListOperatorManager.s.sol @@ -10,11 +10,13 @@ contract DeployAllowListOperatorManager is Script { function run() external { vm.startBroadcast(); - address[] memory operators = new address[](1); - operators[0] = zkBobRelayer; + address[] memory operators = new address[](2); + operators[0] = zkBobProxy; + operators[1] = zkBobProver; - address[] memory feeReceivers = new address[](1); - feeReceivers[0] = zkBobRelayerFeeReceiver; + address[] memory feeReceivers = new address[](2); + feeReceivers[0] = zkBobProxyFeeReceiver; + feeReceivers[1] = zkBobProverFeeReceiver; AllowListOperatorManager operatorManager = new AllowListOperatorManager(operators, feeReceivers, allowListEnabled); diff --git a/script/scripts/Env.s.sol b/script/scripts/Env.s.sol index 7e5be56..e9bcb7e 100644 --- a/script/scripts/Env.s.sol +++ b/script/scripts/Env.s.sol @@ -12,9 +12,9 @@ enum PoolType { } // common -address constant deployer = 0x39F0bD56c1439a22Ee90b4972c16b7868D161981; -address constant admin = 0xd4a3D9Ca00fa1fD8833D560F9217458E61c446d8; -address constant owner = 0xd4a3D9Ca00fa1fD8833D560F9217458E61c446d8; +address constant deployer = 0x37493bFe9c8c31fAbe8615C988e83D59D1a667a9; +address constant admin = 0x37493bFe9c8c31fAbe8615C988e83D59D1a667a9; +address constant owner = 0x37493bFe9c8c31fAbe8615C988e83D59D1a667a9; address constant mockImpl = address(0xdead); // bob @@ -23,19 +23,21 @@ address constant bobVanityAddr = address(0xB0B195aEFA3650A6908f15CdaC7D92F8a5791 bytes32 constant bobSalt = bytes32(uint256(285834900769)); // zkbob -uint256 constant zkBobPoolId = 0; // 0 is reserved for Polygon MVP pool, do not use for other deployments -PoolType constant zkBobPoolType = PoolType.USDC; -string constant zkBobVerifiers = "prodV2"; -address constant zkBobToken = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC +uint256 constant zkBobPoolId = 0xffff0a; // 0 is reserved for Polygon MVP pool, do not use for other deployments +PoolType constant zkBobPoolType = PoolType.BOB; +string constant zkBobVerifiers = "stageV2"; +address constant zkBobToken = 0x2C74B18e2f84B78ac67428d0c7a9898515f0c46f; uint256 constant zkBobInitialRoot = 11469701942666298368112882412133877458305516134926649826543144744382391691533; -address constant zkBobRelayer = 0xc2c4AD59B78F4A0aFD0CDB8133E640Db08Fa5b90; -address constant zkBobRelayerFeeReceiver = 0x758768EC473279c4B1Aa61FA5450745340D4B17d; +address constant zkBobProxy = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; +address constant zkBobProxyFeeReceiver = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; +address constant zkBobProver = 0x7D2D146a7AD3F0Dc398AA718a9bFCa2Bc873a5FD; +address constant zkBobProverFeeReceiver = 0x7D2D146a7AD3F0Dc398AA718a9bFCa2Bc873a5FD; uint256 constant zkBobDirectDepositFee = 0.1 gwei; uint256 constant zkBobDirectDepositTimeout = 1 days; address constant permit2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; // decentralized -uint64 constant gracePeriod = 10 minutes; // TODO +uint64 constant gracePeriod = 3 minutes; // TODO uint64 constant minTreeUpdateFee = 0.1 gwei; bool constant allowListEnabled = true; diff --git a/script/scripts/Local.s.sol b/script/scripts/Local.s.sol index 9ea08e7..5536991 100644 --- a/script/scripts/Local.s.sol +++ b/script/scripts/Local.s.sol @@ -96,11 +96,13 @@ contract DeployLocal is Script { } { - address[] memory operators = new address[](1); - operators[0] = zkBobRelayer; + address[] memory operators = new address[](2); + operators[0] = zkBobProxy; + operators[1] = zkBobProver; - address[] memory feeReceivers = new address[](1); - feeReceivers[0] = zkBobRelayerFeeReceiver; + address[] memory feeReceivers = new address[](2); + feeReceivers[0] = zkBobProxyFeeReceiver; + feeReceivers[1] = zkBobProverFeeReceiver; IOperatorManager operatorManager = new AllowListOperatorManager(operators, feeReceivers, allowListEnabled); pool.setOperatorManager(operatorManager); diff --git a/script/scripts/ZkBobPool.s.sol b/script/scripts/ZkBobPool.s.sol index 5ad5f14..d5fe7b5 100644 --- a/script/scripts/ZkBobPool.s.sol +++ b/script/scripts/ZkBobPool.s.sol @@ -99,11 +99,13 @@ contract DeployZkBobPool is Script { AllowListOperatorManager operatorManager; { - address[] memory operators = new address[](1); - operators[0] = zkBobRelayer; + address[] memory operators = new address[](2); + operators[0] = zkBobProxy; + operators[1] = zkBobProver; - address[] memory feeReceivers = new address[](1); - feeReceivers[0] = zkBobRelayerFeeReceiver; + address[] memory feeReceivers = new address[](2); + feeReceivers[0] = zkBobProxyFeeReceiver; + feeReceivers[1] = zkBobProverFeeReceiver; operatorManager = new AllowListOperatorManager(operators, feeReceivers, allowListEnabled); pool.setOperatorManager(operatorManager); From 5ec30b852c46573ac1dff7633211315b254c37e1 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Mon, 3 Jun 2024 15:48:41 +0300 Subject: [PATCH 35/40] Add decentralization upgrade script --- script/scripts/DecentralizationUpgrade.s.sol | 232 +++++++++++++++++++ script/scripts/Env.s.sol | 2 +- test/scripts/DeployZkBobPoolModules.t.sol | 100 -------- 3 files changed, 233 insertions(+), 101 deletions(-) create mode 100644 script/scripts/DecentralizationUpgrade.s.sol delete mode 100644 test/scripts/DeployZkBobPoolModules.t.sol diff --git a/script/scripts/DecentralizationUpgrade.s.sol b/script/scripts/DecentralizationUpgrade.s.sol new file mode 100644 index 0000000..fbe19d8 --- /dev/null +++ b/script/scripts/DecentralizationUpgrade.s.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.15; + +import "forge-std/Script.sol"; +import "forge-std/Test.sol"; +import "../../src/zkbob/ZkBobPoolUSDC.sol"; +import "../../src/proxy/EIP1967Proxy.sol"; +import "../../src/zkbob/utils/ZkBobAccounting.sol"; +import "../../src/zkbob/manager/AllowListOperatorManager.sol"; + +/** + * @dev OP-USDC pool proxy address. + */ +address constant zkBobPool = 0x1CA8C2B9B20E18e86d5b9a72370fC6c91814c97C; + +/** + * @dev This address will become an owner of the new ZkBobAccounting and AllowListOperatorManager contracts. + */ +address constant owner = 0x14fc6a1a996A2EB889cF86e5c8cD17323bC85290; + +/** + * @dev This value should be sufficient for dedicated prover to update the tree + * but not too big to support liveness. + */ +uint64 constant gracePeriod = 3 minutes; + +/** + * @dev This value should cover the cost of the tree update. + */ +uint64 constant minTreeUpdateFee = 0.1 gwei; + +/** + * @dev AllowListOperatorManager related parameters. + */ +bool constant allowListEnabled = true; +// TODO: Update this addresses before deployment +address constant zkBobProxy1 = 0x7D2D146a7AD3F0Dc398AA718a9bFCa2Bc873a5FD; +address constant zkBobProxy2 = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; +address constant zkBobProxyFeeReceiver1 = 0x7D2D146a7AD3F0Dc398AA718a9bFCa2Bc873a5FD; +address constant zkBobProxyFeeReceiver2 = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; +address constant zkBobProver1 = 0x33a0b018340d6424870cfC686a4d02e1df792254; +address constant zkBobProver2 = 0x63A88E69fa7adEf036fc6ED94394CC9295de2f99; +address constant zkBobProverFeeReceiver1 = 0x33a0b018340d6424870cfC686a4d02e1df792254; +address constant zkBobProverFeeReceiver2 = 0x63A88E69fa7adEf036fc6ED94394CC9295de2f99; + +// Only for checks: +address constant relayer = 0xb9CD01c0b417b4e9095f620aE2f849A84a9B1690; + +contract UpgradeTest is Test { + struct PoolSnapshot { + address owner; + bytes32 slot0; + bytes32 slot1; + uint256 poolIndex; + uint256 oneNullifier; + uint256 lastRoot; + bytes32 all_messages_hash; + uint256 relayerFee; + address tokenSeller; + address kycManager; + } + + function makeSnapshot(ZkBobPoolUSDC _pool) internal view returns (PoolSnapshot memory) { + return PoolSnapshot({ + owner: _pool.owner(), + slot0: vm.load(address(_pool), bytes32(uint256(1))), + slot1: vm.load(address(_pool), bytes32(uint256(2))), + poolIndex: _pool.pool_index(), + oneNullifier: _pool.nullifiers(0x39a833a5c374a0a3328f65ae9a9bf883945694cca613a8415c3a555bda388cd), + lastRoot: _pool.roots(_pool.pool_index()), + all_messages_hash: _pool.all_messages_hash(), + relayerFee: _pool.accumulatedFee(relayer), + tokenSeller: address(_pool.tokenSeller()), + kycManager: address(ZkBobAccounting(address(_pool)).kycProvidersManager()) + }); + } + + function postCheck(ZkBobPoolUSDC _pool, PoolSnapshot memory _snapshot) internal { + assertEq(_snapshot.owner, _pool.owner()); + assertEq(address(_pool.redeemer()), address(0)); // redeemer is not set by script + assertNotEq(address(_pool.accounting()), address(0)); + assertEq(_snapshot.poolIndex, uint256(_pool.pool_index())); + assertEq( + _snapshot.oneNullifier, _pool.nullifiers(0x39a833a5c374a0a3328f65ae9a9bf883945694cca613a8415c3a555bda388cd) + ); + assertEq(_snapshot.lastRoot, _pool.roots(_pool.pool_index())); + assertEq(_snapshot.all_messages_hash, _pool.all_messages_hash()); + assertEq(_snapshot.relayerFee, _pool.accumulatedFee(relayer)); + assertEq(gracePeriod, _pool.gracePeriod()); + assertEq(minTreeUpdateFee, _pool.minTreeUpdateFee()); + assertEq(_snapshot.tokenSeller, address(_pool.tokenSeller())); + assertEq(_snapshot.kycManager, address(ZkBobAccounting(address(_pool.accounting())).kycProvidersManager())); + + checkSlot0(uint256(_snapshot.slot0), ZkBobAccounting(address(_pool.accounting()))); + checkSlot1(uint256(_snapshot.slot1), ZkBobAccounting(address(_pool.accounting()))); + + vm.expectRevert("ZkBobPool: queue is empty"); + _pool.pendingCommitment(); + } + + function checkSlot0(uint256 slot0, ZkBobAccounting accounting) internal { + ( + uint56 maxWeeklyAvgTvl, + uint32 maxWeeklyTxCount, + uint24 tailSlot, + uint24 headSlot, + uint88 cumTvl, + uint32 txCount + ) = accounting.slot0(); + uint24 curSlot = uint24(block.timestamp / 1 hours); + + assertEq(uint56(slot0), maxWeeklyAvgTvl); + assertEq(uint32(slot0 >> 56), maxWeeklyTxCount); + assertEq(curSlot, tailSlot); + assertEq(curSlot, headSlot); + assertEq(uint88(slot0 >> (56 + 32 + 24 + 24)), cumTvl); + assertEq(uint32(slot0 >> (56 + 32 + 24 + 24 + 88)), txCount); + } + + function checkSlot1(uint256 slot1, ZkBobAccounting accounting) internal { + (uint72 tvl) = accounting.slot1(); + assertEq(uint72(slot1), tvl); + } +} + +/** + * @dev Don't forget to set ZkBobPool.TOKEN_NUMERATOR to 1000 for USDC pools. + */ +contract DecentralizationUpgrade is Script, UpgradeTest { + function run() external { + ZkBobPoolUSDC pool = ZkBobPoolUSDC(address(zkBobPool)); + PoolSnapshot memory snapshot = makeSnapshot(pool); + + vm.startBroadcast(); + + // 1. Deploy new ZkBobPoolUSDC implementation + ZkBobPoolUSDC newImpl = new ZkBobPoolUSDC( + pool.pool_id(), + pool.token(), + pool.transfer_verifier(), + pool.tree_verifier(), + pool.batch_deposit_verifier(), + address(pool.direct_deposit_queue()) + ); + + // 2. Upgrade proxy to new implementation + EIP1967Proxy(payable(address(pool))).upgradeTo(address(newImpl)); + + migrateAccounting(address(pool), address(snapshot.kycManager)); + migrateDecentralization(address(pool), address(snapshot.tokenSeller)); + + vm.stopBroadcast(); + + postCheck(ZkBobPoolUSDC(address(pool)), snapshot); + } + + // TODO: Check limits + function migrateAccounting(address _pool, address _kycManager) internal { + // 3. Deploy new ZkBobAccounting implementation + ZkBobAccounting accounting = new ZkBobAccounting(address(_pool), 1_000_000_000); + + bytes memory dump = ZkBobPool(_pool).extsload(bytes32(uint256(1)), 2); + uint32 txCount = uint32(_load(dump, 0, 4)); + uint88 cumTvl = uint88(_load(dump, 4, 11)); + uint32 maxWeeklyTxCount = uint32(_load(dump, 21, 4)); + uint56 maxWeeklyAvgTvl = uint56(_load(dump, 25, 7)); + uint72 tvl = uint72(_load(dump, 55, 9)); + + // 4. Initialize pool index + ZkBobPool(_pool).initializePoolIndex(txCount * 128); + // 5. Set accounting + ZkBobPool(_pool).setAccounting(IZkBobAccounting(accounting)); + + // 6. Initialize accounting + ZkBobAccounting(accounting).initialize(txCount, tvl, cumTvl, maxWeeklyTxCount, maxWeeklyAvgTvl); + // 7. Set kyc providers manager + ZkBobAccounting(accounting).setKycProvidersManager(IKycProvidersManager(_kycManager)); + // 8. Set limits for tier 0 + ZkBobAccounting(accounting).setLimits( + 0, 2_000_000 gwei, 300_000 gwei, 300_000 gwei, 10_000 gwei, 10_000 gwei, 10_000 gwei, 1_000 gwei + ); + // 9. Set limits for tier 1 + ZkBobAccounting(accounting).setLimits( + 1, 2_000_000 gwei, 300_000 gwei, 300_000 gwei, 100_000 gwei, 100_000 gwei, 10_000 gwei, 1_000 gwei + ); + // 10. Set limits for tier 254 + ZkBobAccounting(accounting).setLimits( + 254, 2_000_000 gwei, 300_000 gwei, 300_000 gwei, 20_000 gwei, 20_000 gwei, 10_000 gwei, 1_000 gwei + ); + + // 11. Transfer accounting accounting ownership to the owner + accounting.transferOwnership(owner); + } + + function migrateDecentralization(address _pool, address _tokenSeller) internal { + // 12. Set grace period + ZkBobPool(_pool).setGracePeriod(gracePeriod); + // 13. Set min tree update fee + ZkBobPool(_pool).setMinTreeUpdateFee(minTreeUpdateFee); + // 14. Set token seller + ZkBobPoolUSDC(_pool).setTokenSeller(_tokenSeller); + + // 15. Deploy AllowListOperatorManager + address[] memory operators = new address[](4); + operators[0] = zkBobProxy1; + operators[1] = zkBobProver1; + operators[2] = zkBobProxy2; + operators[3] = zkBobProver2; + + address[] memory feeReceivers = new address[](4); + feeReceivers[0] = zkBobProxyFeeReceiver1; + feeReceivers[1] = zkBobProverFeeReceiver1; + feeReceivers[2] = zkBobProxyFeeReceiver2; + feeReceivers[3] = zkBobProverFeeReceiver2; + + AllowListOperatorManager operatorManager = + new AllowListOperatorManager(operators, feeReceivers, allowListEnabled); + + // 16. Set operator manager + ZkBobPool(_pool).setOperatorManager(operatorManager); + + // 17. Transfer operator manager ownership to the owner + operatorManager.transferOwnership(owner); + } + + function _load(bytes memory _dump, uint256 _from, uint256 _len) internal pure returns (uint256 res) { + assembly { + res := shr(sub(256, shl(3, _len)), mload(add(_dump, add(32, _from)))) + } + } +} diff --git a/script/scripts/Env.s.sol b/script/scripts/Env.s.sol index e9bcb7e..8143cd6 100644 --- a/script/scripts/Env.s.sol +++ b/script/scripts/Env.s.sol @@ -26,7 +26,7 @@ bytes32 constant bobSalt = bytes32(uint256(285834900769)); uint256 constant zkBobPoolId = 0xffff0a; // 0 is reserved for Polygon MVP pool, do not use for other deployments PoolType constant zkBobPoolType = PoolType.BOB; string constant zkBobVerifiers = "stageV2"; -address constant zkBobToken = 0x2C74B18e2f84B78ac67428d0c7a9898515f0c46f; +address constant zkBobToken = 0x2C74B18e2f84B78ac67428d0c7a9898515f0c46f; uint256 constant zkBobInitialRoot = 11469701942666298368112882412133877458305516134926649826543144744382391691533; address constant zkBobProxy = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; address constant zkBobProxyFeeReceiver = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; diff --git a/test/scripts/DeployZkBobPoolModules.t.sol b/test/scripts/DeployZkBobPoolModules.t.sol deleted file mode 100644 index 2a69f2c..0000000 --- a/test/scripts/DeployZkBobPoolModules.t.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -import {Test, console} from "forge-std/Test.sol"; -import {AbstractOptimismForkTest} from "../shared/ForkTests.t.sol"; -import {ZkBobPoolUSDC} from "../../src/zkbob/ZkBobPoolUSDC.sol"; -import {DeployZkBobPoolModules} from "../../script/scripts/DeployZkBobPoolModules.s.sol"; -import {EIP1967Proxy} from "../../src/proxy/EIP1967Proxy.sol"; -import {ZkBobAccounting} from "../../src/zkbob/utils/ZkBobAccounting.sol"; - -contract DeployZkBobPoolModulesTest is AbstractOptimismForkTest { - struct PoolStateCheck { - address owner; - bytes32 slot0; - bytes32 slot1; - address operatorManager; - uint256 poolIndex; - uint256 oneNullifier; - uint256 lastRoot; - bytes32 all_messages_hash; - uint256 relayerFee; - address tokenSeller; - } - - function testOptimismUSDCPoolUpgrade() public { - vm.createSelectFork(forkRpcUrl, 116204904); - ZkBobPoolUSDC pool = ZkBobPoolUSDC(0x1CA8C2B9B20E18e86d5b9a72370fC6c91814c97C); - address relayer = 0xb9CD01c0b417b4e9095f620aE2f849A84a9B1690; - - DeployZkBobPoolModules upgrade = new DeployZkBobPoolModules(); - - // DeployZkBobPoolModules assumes that proxyAdmin is the owner of the pool - address proxyAdmin = EIP1967Proxy(payable(address(pool))).admin(); - address owner = pool.owner(); - vm.prank(owner); - pool.transferOwnership(address(proxyAdmin)); - - // stack to deep - PoolStateCheck memory poolState = PoolStateCheck({ - owner: pool.owner(), - slot0: vm.load(address(pool), bytes32(uint256(1))), - slot1: vm.load(address(pool), bytes32(uint256(2))), - operatorManager: address(pool.operatorManager()), - poolIndex: pool.pool_index(), - oneNullifier: pool.nullifiers(0x39a833a5c374a0a3328f65ae9a9bf883945694cca613a8415c3a555bda388cd), - lastRoot: pool.roots(pool.pool_index()), - all_messages_hash: pool.all_messages_hash(), - relayerFee: pool.accumulatedFee(relayer), - tokenSeller: address(pool.tokenSeller()) - }); - - startHoax(proxyAdmin); - upgrade.runWithPoolAddress(address(pool), false); - vm.stopPrank(); - - assertEq(poolState.owner, pool.owner()); - assertEq(address(pool.redeemer()), address(0)); // redeemer is not set by script - assertNotEq(address(pool.accounting()), address(0)); - assertEq(poolState.poolIndex, uint256(pool.pool_index())); - assertEq(poolState.operatorManager, address(pool.operatorManager())); - assertEq( - poolState.oneNullifier, pool.nullifiers(0x39a833a5c374a0a3328f65ae9a9bf883945694cca613a8415c3a555bda388cd) - ); - assertEq(poolState.lastRoot, pool.roots(pool.pool_index())); - assertEq(poolState.all_messages_hash, pool.all_messages_hash()); - assertEq(poolState.relayerFee, pool.accumulatedFee(relayer)); - assertEq(10 minutes, pool.gracePeriod()); - assertEq(0.1 gwei, pool.minTreeUpdateFee()); - assertEq(poolState.tokenSeller, address(pool.tokenSeller())); - - checkSlot0(uint256(poolState.slot0), ZkBobAccounting(address(pool.accounting()))); - checkSlot1(uint256(poolState.slot1), ZkBobAccounting(address(pool.accounting()))); - - vm.expectRevert("ZkBobPool: queue is empty"); - pool.pendingCommitment(); - } - - function checkSlot0(uint256 slot0, ZkBobAccounting accounting) internal { - ( - uint56 maxWeeklyAvgTvl, - uint32 maxWeeklyTxCount, - uint24 tailSlot, - uint24 headSlot, - uint88 cumTvl, - uint32 txCount - ) = accounting.slot0(); - uint24 curSlot = uint24(block.timestamp / 1 hours); - - assertEq(uint56(slot0), maxWeeklyAvgTvl); - assertEq(uint32(slot0 >> 56), maxWeeklyTxCount); - assertEq(curSlot, tailSlot); - assertEq(curSlot, headSlot); - assertEq(uint88(slot0 >> (56 + 32 + 24 + 24)), cumTvl); - assertEq(uint32(slot0 >> (56 + 32 + 24 + 24 + 88)), txCount); - } - - function checkSlot1(uint256 slot1, ZkBobAccounting accounting) internal { - (uint72 tvl) = accounting.slot1(); - assertEq(uint72(slot1), tvl); - } -} From 22644a7bcbb676a26ee94f6ae13f4298ceb4bd29 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Wed, 5 Jun 2024 17:11:15 +0300 Subject: [PATCH 36/40] Modify migration script --- ...de.s.sol => MigrateDecentralization.s.sol} | 108 +++--------------- 1 file changed, 15 insertions(+), 93 deletions(-) rename script/scripts/{DecentralizationUpgrade.s.sol => MigrateDecentralization.s.sol} (55%) diff --git a/script/scripts/DecentralizationUpgrade.s.sol b/script/scripts/MigrateDecentralization.s.sol similarity index 55% rename from script/scripts/DecentralizationUpgrade.s.sol rename to script/scripts/MigrateDecentralization.s.sol index fbe19d8..5ffe925 100644 --- a/script/scripts/DecentralizationUpgrade.s.sol +++ b/script/scripts/MigrateDecentralization.s.sol @@ -17,7 +17,7 @@ address constant zkBobPool = 0x1CA8C2B9B20E18e86d5b9a72370fC6c91814c97C; /** * @dev This address will become an owner of the new ZkBobAccounting and AllowListOperatorManager contracts. */ -address constant owner = 0x14fc6a1a996A2EB889cF86e5c8cD17323bC85290; +address constant operatorManagerOwner = 0x14fc6a1a996A2EB889cF86e5c8cD17323bC85290; /** * @dev This value should be sufficient for dedicated prover to update the tree @@ -36,12 +36,12 @@ uint64 constant minTreeUpdateFee = 0.1 gwei; bool constant allowListEnabled = true; // TODO: Update this addresses before deployment address constant zkBobProxy1 = 0x7D2D146a7AD3F0Dc398AA718a9bFCa2Bc873a5FD; -address constant zkBobProxy2 = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; address constant zkBobProxyFeeReceiver1 = 0x7D2D146a7AD3F0Dc398AA718a9bFCa2Bc873a5FD; +address constant zkBobProxy2 = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; address constant zkBobProxyFeeReceiver2 = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; address constant zkBobProver1 = 0x33a0b018340d6424870cfC686a4d02e1df792254; -address constant zkBobProver2 = 0x63A88E69fa7adEf036fc6ED94394CC9295de2f99; address constant zkBobProverFeeReceiver1 = 0x33a0b018340d6424870cfC686a4d02e1df792254; +address constant zkBobProver2 = 0x63A88E69fa7adEf036fc6ED94394CC9295de2f99; address constant zkBobProverFeeReceiver2 = 0x63A88E69fa7adEf036fc6ED94394CC9295de2f99; // Only for checks: @@ -50,36 +50,30 @@ address constant relayer = 0xb9CD01c0b417b4e9095f620aE2f849A84a9B1690; contract UpgradeTest is Test { struct PoolSnapshot { address owner; - bytes32 slot0; - bytes32 slot1; uint256 poolIndex; uint256 oneNullifier; uint256 lastRoot; bytes32 all_messages_hash; uint256 relayerFee; address tokenSeller; - address kycManager; + address accounting; } function makeSnapshot(ZkBobPoolUSDC _pool) internal view returns (PoolSnapshot memory) { return PoolSnapshot({ owner: _pool.owner(), - slot0: vm.load(address(_pool), bytes32(uint256(1))), - slot1: vm.load(address(_pool), bytes32(uint256(2))), poolIndex: _pool.pool_index(), oneNullifier: _pool.nullifiers(0x39a833a5c374a0a3328f65ae9a9bf883945694cca613a8415c3a555bda388cd), lastRoot: _pool.roots(_pool.pool_index()), all_messages_hash: _pool.all_messages_hash(), relayerFee: _pool.accumulatedFee(relayer), tokenSeller: address(_pool.tokenSeller()), - kycManager: address(ZkBobAccounting(address(_pool)).kycProvidersManager()) + accounting: address(_pool.accounting()) }); } function postCheck(ZkBobPoolUSDC _pool, PoolSnapshot memory _snapshot) internal { assertEq(_snapshot.owner, _pool.owner()); - assertEq(address(_pool.redeemer()), address(0)); // redeemer is not set by script - assertNotEq(address(_pool.accounting()), address(0)); assertEq(_snapshot.poolIndex, uint256(_pool.pool_index())); assertEq( _snapshot.oneNullifier, _pool.nullifiers(0x39a833a5c374a0a3328f65ae9a9bf883945694cca613a8415c3a555bda388cd) @@ -87,47 +81,20 @@ contract UpgradeTest is Test { assertEq(_snapshot.lastRoot, _pool.roots(_pool.pool_index())); assertEq(_snapshot.all_messages_hash, _pool.all_messages_hash()); assertEq(_snapshot.relayerFee, _pool.accumulatedFee(relayer)); + assertEq(_snapshot.tokenSeller, address(_pool.tokenSeller())); + assertEq(_snapshot.accounting, address(_pool.accounting())); assertEq(gracePeriod, _pool.gracePeriod()); assertEq(minTreeUpdateFee, _pool.minTreeUpdateFee()); - assertEq(_snapshot.tokenSeller, address(_pool.tokenSeller())); - assertEq(_snapshot.kycManager, address(ZkBobAccounting(address(_pool.accounting())).kycProvidersManager())); - - checkSlot0(uint256(_snapshot.slot0), ZkBobAccounting(address(_pool.accounting()))); - checkSlot1(uint256(_snapshot.slot1), ZkBobAccounting(address(_pool.accounting()))); vm.expectRevert("ZkBobPool: queue is empty"); _pool.pendingCommitment(); } - - function checkSlot0(uint256 slot0, ZkBobAccounting accounting) internal { - ( - uint56 maxWeeklyAvgTvl, - uint32 maxWeeklyTxCount, - uint24 tailSlot, - uint24 headSlot, - uint88 cumTvl, - uint32 txCount - ) = accounting.slot0(); - uint24 curSlot = uint24(block.timestamp / 1 hours); - - assertEq(uint56(slot0), maxWeeklyAvgTvl); - assertEq(uint32(slot0 >> 56), maxWeeklyTxCount); - assertEq(curSlot, tailSlot); - assertEq(curSlot, headSlot); - assertEq(uint88(slot0 >> (56 + 32 + 24 + 24)), cumTvl); - assertEq(uint32(slot0 >> (56 + 32 + 24 + 24 + 88)), txCount); - } - - function checkSlot1(uint256 slot1, ZkBobAccounting accounting) internal { - (uint72 tvl) = accounting.slot1(); - assertEq(uint72(slot1), tvl); - } } /** * @dev Don't forget to set ZkBobPool.TOKEN_NUMERATOR to 1000 for USDC pools. */ -contract DecentralizationUpgrade is Script, UpgradeTest { +contract MigrateDecentralization is Script, UpgradeTest { function run() external { ZkBobPoolUSDC pool = ZkBobPoolUSDC(address(zkBobPool)); PoolSnapshot memory snapshot = makeSnapshot(pool); @@ -147,7 +114,6 @@ contract DecentralizationUpgrade is Script, UpgradeTest { // 2. Upgrade proxy to new implementation EIP1967Proxy(payable(address(pool))).upgradeTo(address(newImpl)); - migrateAccounting(address(pool), address(snapshot.kycManager)); migrateDecentralization(address(pool), address(snapshot.tokenSeller)); vm.stopBroadcast(); @@ -155,53 +121,15 @@ contract DecentralizationUpgrade is Script, UpgradeTest { postCheck(ZkBobPoolUSDC(address(pool)), snapshot); } - // TODO: Check limits - function migrateAccounting(address _pool, address _kycManager) internal { - // 3. Deploy new ZkBobAccounting implementation - ZkBobAccounting accounting = new ZkBobAccounting(address(_pool), 1_000_000_000); - - bytes memory dump = ZkBobPool(_pool).extsload(bytes32(uint256(1)), 2); - uint32 txCount = uint32(_load(dump, 0, 4)); - uint88 cumTvl = uint88(_load(dump, 4, 11)); - uint32 maxWeeklyTxCount = uint32(_load(dump, 21, 4)); - uint56 maxWeeklyAvgTvl = uint56(_load(dump, 25, 7)); - uint72 tvl = uint72(_load(dump, 55, 9)); - - // 4. Initialize pool index - ZkBobPool(_pool).initializePoolIndex(txCount * 128); - // 5. Set accounting - ZkBobPool(_pool).setAccounting(IZkBobAccounting(accounting)); - - // 6. Initialize accounting - ZkBobAccounting(accounting).initialize(txCount, tvl, cumTvl, maxWeeklyTxCount, maxWeeklyAvgTvl); - // 7. Set kyc providers manager - ZkBobAccounting(accounting).setKycProvidersManager(IKycProvidersManager(_kycManager)); - // 8. Set limits for tier 0 - ZkBobAccounting(accounting).setLimits( - 0, 2_000_000 gwei, 300_000 gwei, 300_000 gwei, 10_000 gwei, 10_000 gwei, 10_000 gwei, 1_000 gwei - ); - // 9. Set limits for tier 1 - ZkBobAccounting(accounting).setLimits( - 1, 2_000_000 gwei, 300_000 gwei, 300_000 gwei, 100_000 gwei, 100_000 gwei, 10_000 gwei, 1_000 gwei - ); - // 10. Set limits for tier 254 - ZkBobAccounting(accounting).setLimits( - 254, 2_000_000 gwei, 300_000 gwei, 300_000 gwei, 20_000 gwei, 20_000 gwei, 10_000 gwei, 1_000 gwei - ); - - // 11. Transfer accounting accounting ownership to the owner - accounting.transferOwnership(owner); - } - function migrateDecentralization(address _pool, address _tokenSeller) internal { - // 12. Set grace period + // 3. Set grace period ZkBobPool(_pool).setGracePeriod(gracePeriod); - // 13. Set min tree update fee + // 4. Set min tree update fee ZkBobPool(_pool).setMinTreeUpdateFee(minTreeUpdateFee); - // 14. Set token seller + // 5. Set token seller ZkBobPoolUSDC(_pool).setTokenSeller(_tokenSeller); - // 15. Deploy AllowListOperatorManager + // 6. Deploy AllowListOperatorManager address[] memory operators = new address[](4); operators[0] = zkBobProxy1; operators[1] = zkBobProver1; @@ -217,16 +145,10 @@ contract DecentralizationUpgrade is Script, UpgradeTest { AllowListOperatorManager operatorManager = new AllowListOperatorManager(operators, feeReceivers, allowListEnabled); - // 16. Set operator manager + // 7. Set operator manager ZkBobPool(_pool).setOperatorManager(operatorManager); - // 17. Transfer operator manager ownership to the owner - operatorManager.transferOwnership(owner); - } - - function _load(bytes memory _dump, uint256 _from, uint256 _len) internal pure returns (uint256 res) { - assembly { - res := shr(sub(256, shl(3, _len)), mload(add(_dump, add(32, _from)))) - } + // 8. Transfer operator manager ownership to the owner + operatorManager.transferOwnership(operatorManagerOwner); } } From 8f37f9f0d0f7b8fce179935346615e4dd7a1e955 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Thu, 4 Jul 2024 12:10:40 +0300 Subject: [PATCH 37/40] Update scripts --- .../DeployAllowListOperatorManager.s.sol | 34 ++++++-- script/scripts/MigrateDecentralization.s.sol | 77 ++++--------------- script/scripts/NewZkBobPoolUSDCImpl.s.sol | 32 ++++++++ 3 files changed, 73 insertions(+), 70 deletions(-) create mode 100644 script/scripts/NewZkBobPoolUSDCImpl.s.sol diff --git a/script/scripts/DeployAllowListOperatorManager.s.sol b/script/scripts/DeployAllowListOperatorManager.s.sol index 80a1f3e..9817058 100644 --- a/script/scripts/DeployAllowListOperatorManager.s.sol +++ b/script/scripts/DeployAllowListOperatorManager.s.sol @@ -3,26 +3,46 @@ pragma solidity 0.8.15; import "forge-std/Script.sol"; -import "./Env.s.sol"; import {AllowListOperatorManager} from "../../src/zkbob/manager/AllowListOperatorManager.sol"; +// TODO: Update this values before the deployment +address constant operatorManagerOwner = 0x14fc6a1a996A2EB889cF86e5c8cD17323bC85290; +address constant zkBobProxy1 = 0x7D2D146a7AD3F0Dc398AA718a9bFCa2Bc873a5FD; +address constant zkBobProxyFeeReceiver1 = 0x7D2D146a7AD3F0Dc398AA718a9bFCa2Bc873a5FD; +address constant zkBobProxy2 = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; +address constant zkBobProxyFeeReceiver2 = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; +address constant zkBobProver1 = 0x33a0b018340d6424870cfC686a4d02e1df792254; +address constant zkBobProverFeeReceiver1 = 0x33a0b018340d6424870cfC686a4d02e1df792254; +address constant zkBobProver2 = 0x63A88E69fa7adEf036fc6ED94394CC9295de2f99; +address constant zkBobProverFeeReceiver2 = 0x63A88E69fa7adEf036fc6ED94394CC9295de2f99; + +bool constant allowListEnabled = true; + contract DeployAllowListOperatorManager is Script { function run() external { vm.startBroadcast(); - address[] memory operators = new address[](2); - operators[0] = zkBobProxy; - operators[1] = zkBobProver; + address[] memory operators = new address[](4); + operators[0] = zkBobProxy1; + operators[1] = zkBobProver1; + operators[2] = zkBobProxy2; + operators[3] = zkBobProver2; - address[] memory feeReceivers = new address[](2); - feeReceivers[0] = zkBobProxyFeeReceiver; - feeReceivers[1] = zkBobProverFeeReceiver; + address[] memory feeReceivers = new address[](4); + feeReceivers[0] = zkBobProxyFeeReceiver1; + feeReceivers[1] = zkBobProverFeeReceiver1; + feeReceivers[2] = zkBobProxyFeeReceiver2; + feeReceivers[3] = zkBobProverFeeReceiver2; AllowListOperatorManager operatorManager = new AllowListOperatorManager(operators, feeReceivers, allowListEnabled); + operatorManager.transferOwnership(operatorManagerOwner); + vm.stopBroadcast(); + assert(address(operatorManager.owner()) == operatorManagerOwner); + console2.log("AllowListOperatorManager address:", address(operatorManager)); } } diff --git a/script/scripts/MigrateDecentralization.s.sol b/script/scripts/MigrateDecentralization.s.sol index 5ffe925..e511582 100644 --- a/script/scripts/MigrateDecentralization.s.sol +++ b/script/scripts/MigrateDecentralization.s.sol @@ -9,16 +9,15 @@ import "../../src/proxy/EIP1967Proxy.sol"; import "../../src/zkbob/utils/ZkBobAccounting.sol"; import "../../src/zkbob/manager/AllowListOperatorManager.sol"; +// TODO: update this parameters before running the script +address constant newZkBobPoolImpl = 0x0114Bf30d9f5A7f503D3DFC65534F2B5AC302c85; +address constant newOperatorManager = 0xFd5a6a67D768d5BF1A8c7724387CA8786Bd4DD91; + /** * @dev OP-USDC pool proxy address. */ address constant zkBobPool = 0x1CA8C2B9B20E18e86d5b9a72370fC6c91814c97C; -/** - * @dev This address will become an owner of the new ZkBobAccounting and AllowListOperatorManager contracts. - */ -address constant operatorManagerOwner = 0x14fc6a1a996A2EB889cF86e5c8cD17323bC85290; - /** * @dev This value should be sufficient for dedicated prover to update the tree * but not too big to support liveness. @@ -30,20 +29,6 @@ uint64 constant gracePeriod = 3 minutes; */ uint64 constant minTreeUpdateFee = 0.1 gwei; -/** - * @dev AllowListOperatorManager related parameters. - */ -bool constant allowListEnabled = true; -// TODO: Update this addresses before deployment -address constant zkBobProxy1 = 0x7D2D146a7AD3F0Dc398AA718a9bFCa2Bc873a5FD; -address constant zkBobProxyFeeReceiver1 = 0x7D2D146a7AD3F0Dc398AA718a9bFCa2Bc873a5FD; -address constant zkBobProxy2 = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; -address constant zkBobProxyFeeReceiver2 = 0xFec49782FE8e11De9Fb3Ba645a76FE914FFfe3cb; -address constant zkBobProver1 = 0x33a0b018340d6424870cfC686a4d02e1df792254; -address constant zkBobProverFeeReceiver1 = 0x33a0b018340d6424870cfC686a4d02e1df792254; -address constant zkBobProver2 = 0x63A88E69fa7adEf036fc6ED94394CC9295de2f99; -address constant zkBobProverFeeReceiver2 = 0x63A88E69fa7adEf036fc6ED94394CC9295de2f99; - // Only for checks: address constant relayer = 0xb9CD01c0b417b4e9095f620aE2f849A84a9B1690; @@ -101,54 +86,20 @@ contract MigrateDecentralization is Script, UpgradeTest { vm.startBroadcast(); - // 1. Deploy new ZkBobPoolUSDC implementation - ZkBobPoolUSDC newImpl = new ZkBobPoolUSDC( - pool.pool_id(), - pool.token(), - pool.transfer_verifier(), - pool.tree_verifier(), - pool.batch_deposit_verifier(), - address(pool.direct_deposit_queue()) - ); - - // 2. Upgrade proxy to new implementation - EIP1967Proxy(payable(address(pool))).upgradeTo(address(newImpl)); + // 1. Upgrade proxy to new implementation + EIP1967Proxy(payable(address(pool))).upgradeTo(address(newZkBobPoolImpl)); - migrateDecentralization(address(pool), address(snapshot.tokenSeller)); + // 2. Set grace period + ZkBobPool(pool).setGracePeriod(gracePeriod); + // 3. Set min tree update fee + ZkBobPool(pool).setMinTreeUpdateFee(minTreeUpdateFee); + // 4. Set token seller + ZkBobPoolUSDC(pool).setTokenSeller(snapshot.tokenSeller); + // 5. Set operator manager + ZkBobPool(pool).setOperatorManager(AllowListOperatorManager(newOperatorManager)); vm.stopBroadcast(); postCheck(ZkBobPoolUSDC(address(pool)), snapshot); } - - function migrateDecentralization(address _pool, address _tokenSeller) internal { - // 3. Set grace period - ZkBobPool(_pool).setGracePeriod(gracePeriod); - // 4. Set min tree update fee - ZkBobPool(_pool).setMinTreeUpdateFee(minTreeUpdateFee); - // 5. Set token seller - ZkBobPoolUSDC(_pool).setTokenSeller(_tokenSeller); - - // 6. Deploy AllowListOperatorManager - address[] memory operators = new address[](4); - operators[0] = zkBobProxy1; - operators[1] = zkBobProver1; - operators[2] = zkBobProxy2; - operators[3] = zkBobProver2; - - address[] memory feeReceivers = new address[](4); - feeReceivers[0] = zkBobProxyFeeReceiver1; - feeReceivers[1] = zkBobProverFeeReceiver1; - feeReceivers[2] = zkBobProxyFeeReceiver2; - feeReceivers[3] = zkBobProverFeeReceiver2; - - AllowListOperatorManager operatorManager = - new AllowListOperatorManager(operators, feeReceivers, allowListEnabled); - - // 7. Set operator manager - ZkBobPool(_pool).setOperatorManager(operatorManager); - - // 8. Transfer operator manager ownership to the owner - operatorManager.transferOwnership(operatorManagerOwner); - } } diff --git a/script/scripts/NewZkBobPoolUSDCImpl.s.sol b/script/scripts/NewZkBobPoolUSDCImpl.s.sol new file mode 100644 index 0000000..50ae05e --- /dev/null +++ b/script/scripts/NewZkBobPoolUSDCImpl.s.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.15; + +import "forge-std/Script.sol"; +import "../../src/zkbob/ZkBobPoolUSDC.sol"; + +/** + * @dev OP-USDC pool proxy address. + */ +address constant zkBobPool = 0x1CA8C2B9B20E18e86d5b9a72370fC6c91814c97C; + +contract DeployNewZkBobPoolUSDCImpl is Script { + function run() external { + ZkBobPoolUSDC pool = ZkBobPoolUSDC(payable(zkBobPool)); + + vm.startBroadcast(); + + ZkBobPoolUSDC newImpl = new ZkBobPoolUSDC( + pool.pool_id(), + pool.token(), + pool.transfer_verifier(), + pool.tree_verifier(), + pool.batch_deposit_verifier(), + address(pool.direct_deposit_queue()) + ); + + vm.stopBroadcast(); + + console2.log("ZkBobPoolUSDC implementation:", address(newImpl)); + } +} From b864c8e17788e0b97f2f8ea1ece3475486fcd1b2 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Mon, 19 Aug 2024 14:41:26 +0300 Subject: [PATCH 38/40] Minor fixes --- script/scripts/DeployZkBobPoolModules.s.sol | 106 -------------------- src/zkbob/ZkBobPool.sol | 6 +- test/shared/Env.t.sol | 2 +- 3 files changed, 4 insertions(+), 110 deletions(-) delete mode 100644 script/scripts/DeployZkBobPoolModules.s.sol diff --git a/script/scripts/DeployZkBobPoolModules.s.sol b/script/scripts/DeployZkBobPoolModules.s.sol deleted file mode 100644 index 7ccb5ad..0000000 --- a/script/scripts/DeployZkBobPoolModules.s.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.15; - -import "forge-std/Script.sol"; -import "forge-std/Test.sol"; -import "./Env.s.sol"; -import "../../src/zkbob/ZkBobPool.sol"; -import "../../src/zkbob/utils/ZkBobAccounting.sol"; -import "../../src/proxy/EIP1967Proxy.sol"; -import "../../src/zkbob/ZkBobPoolUSDC.sol"; - -contract DummyDelegateCall { - function delegate(address to, bytes calldata data) external { - (bool status,) = address(to).delegatecall(data); - require(status); - } -} - -contract Migrator { - function migrate(address _target, address _newImpl, address _accounting, address _tokenSeller) external { - address kycManager = address(ZkBobAccounting(_target).kycProvidersManager()); - - EIP1967Proxy(payable(_target)).upgradeTo(_newImpl); - - bytes memory dump = ZkBobPool(_target).extsload(bytes32(uint256(1)), 2); - uint32 txCount = uint32(_load(dump, 0, 4)); - uint88 cumTvl = uint88(_load(dump, 4, 11)); - uint32 maxWeeklyTxCount = uint32(_load(dump, 21, 4)); - uint56 maxWeeklyAvgTvl = uint56(_load(dump, 25, 7)); - uint72 tvl = uint72(_load(dump, 55, 9)); - - ZkBobPool(_target).initializePoolIndex(txCount * 128); - ZkBobPool(_target).setAccounting(IZkBobAccounting(_accounting)); - ZkBobPool(_target).setGracePeriod(gracePeriod); - ZkBobPool(_target).setMinTreeUpdateFee(minTreeUpdateFee); - ZkBobPoolUSDC(_target).setTokenSeller(_tokenSeller); - - ZkBobAccounting(_accounting).initialize(txCount, tvl, cumTvl, maxWeeklyTxCount, maxWeeklyAvgTvl); - ZkBobAccounting(_accounting).setKycProvidersManager(IKycProvidersManager(kycManager)); - ZkBobAccounting(_accounting).setLimits( - 0, 2_000_000 gwei, 300_000 gwei, 300_000 gwei, 10_000 gwei, 10_000 gwei, 10_000 gwei, 1_000 gwei - ); - ZkBobAccounting(_accounting).setLimits( - 1, 2_000_000 gwei, 300_000 gwei, 300_000 gwei, 100_000 gwei, 100_000 gwei, 10_000 gwei, 1_000 gwei - ); - ZkBobAccounting(_accounting).setLimits( - 254, 2_000_000 gwei, 300_000 gwei, 300_000 gwei, 20_000 gwei, 20_000 gwei, 10_000 gwei, 1_000 gwei - ); - } - - function _load(bytes memory _dump, uint256 _from, uint256 _len) internal returns (uint256 res) { - assembly { - res := shr(sub(256, shl(3, _len)), mload(add(_dump, add(32, _from)))) - } - } -} - -/** - * @dev This script assumes that pool.owner == proxyAdmin - * @dev This script uses gracePeriod and minTreeUpdateFee from Env.s.sol - */ -contract DeployZkBobPoolModules is Script, Test { - function run() external { - runWithPoolAddress(address(zkBobPool), true); - } - - function runWithPoolAddress(address poolAddress, bool broadcast) public { - ZkBobPoolUSDC pool = ZkBobPoolUSDC(poolAddress); - address owner = pool.owner(); - vm.etch(owner, type(DummyDelegateCall).runtimeCode); - - address tokenSeller = address(pool.tokenSeller()); - uint256 poolIndex = uint256(pool.pool_index()); - - if (broadcast) { - vm.startBroadcast(); - } - - ZkBobPoolUSDC impl = new ZkBobPoolUSDC( - pool.pool_id(), - pool.token(), - pool.transfer_verifier(), - pool.tree_verifier(), - pool.batch_deposit_verifier(), - address(pool.direct_deposit_queue()) - ); - Migrator mig = new Migrator(); - ZkBobAccounting acc = new ZkBobAccounting(address(pool), 1_000_000_000); - acc.transferOwnership(owner); - DummyDelegateCall(owner).delegate( - address(mig), - abi.encodeWithSelector(Migrator.migrate.selector, address(pool), address(impl), address(acc), tokenSeller) - ); - - if (broadcast) { - vm.stopBroadcast(); - } - - acc.slot0(); - acc.slot1(); - - assertEq(address(pool.tokenSeller()), tokenSeller); - assertEq(uint256(pool.pool_index()), poolIndex); - } -} diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index 7055b6a..c4852b9 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -72,7 +72,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex Queue.CommitmentQueue internal pendingCommitments; /** - * @dev The duration of the grace period within which only the prover who submitted the transaction + * @dev The duration of the grace period during which only the privileged prover * can submit the tree update proof. */ uint64 public gracePeriod; @@ -183,7 +183,6 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex returns (uint256 commitment, address privilegedProver, uint64 fee, uint64 timestamp, uint64 gracePeriodEnd) { PendingCommitment memory op = pendingCommitments.front(); - require(op.commitment != 0, "ZkBobPool: no pending commitment"); gracePeriodEnd = op.timestamp + gracePeriod; return (op.commitment, op.prover, op.fee, op.timestamp, gracePeriodEnd); } @@ -369,6 +368,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex * @param _indices list of indices for queued pending deposits. * @param _out_commit out commitment for output notes serialized from direct deposits. * @param _batch_deposit_proof snark proof for batch deposit verifier. + * @param _prover address of the privileged prover */ function appendDirectDeposits( uint256[] calldata _indices, @@ -415,7 +415,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex /** * @dev Updates pool index and merkle tree root if the provided proof is valid and * the proof corresponds to the pending commitment. - * A prover that submitted the transfer proof has the grace period to submit the tree update proof. + * A prover specified in the pending commitment has a grace period to submit the tree update proof. * @param _commitment pending commitment to be proven. * @param _proof snark proof for tree update verifier. * @param _rootAfter new merkle tree root. diff --git a/test/shared/Env.t.sol b/test/shared/Env.t.sol index 1d1cbef..b2191cf 100644 --- a/test/shared/Env.t.sol +++ b/test/shared/Env.t.sol @@ -19,6 +19,6 @@ bytes32 constant bobSalt = bytes32(uint256(285834900769)); uint256 constant forkBlockMainnet = 16200000; string constant forkRpcUrlMainnet = "https://eth.llamarpc.com"; uint256 constant forkBlockPolygon = 37000000; -string constant forkRpcUrlPolygon = "https://polygon.llamarpc.com"; +string constant forkRpcUrlPolygon = "https://polygon-rpc.com"; uint256 constant forkBlockOptimism = 52000000; string constant forkRpcUrlOptimism = "https://optimism.llamarpc.com"; From 78eb1bd352980e72d7f6c5833623b8e673090d62 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Mon, 19 Aug 2024 14:42:48 +0300 Subject: [PATCH 39/40] Fix format --- src/zkbob/ZkBobPool.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index c4852b9..4435aff 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -72,7 +72,7 @@ abstract contract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, Ex Queue.CommitmentQueue internal pendingCommitments; /** - * @dev The duration of the grace period during which only the privileged prover + * @dev The duration of the grace period during which only the privileged prover * can submit the tree update proof. */ uint64 public gracePeriod; From 8428fa2a29c61f2a8cc392e792c868c76b827df2 Mon Sep 17 00:00:00 2001 From: Alexander Filippov Date: Mon, 19 Aug 2024 14:45:30 +0300 Subject: [PATCH 40/40] Fix format --- test/zkbob/ZkBobPoolDecentralized.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/zkbob/ZkBobPoolDecentralized.t.sol b/test/zkbob/ZkBobPoolDecentralized.t.sol index 0801cbb..58c7b9e 100644 --- a/test/zkbob/ZkBobPoolDecentralized.t.sol +++ b/test/zkbob/ZkBobPoolDecentralized.t.sol @@ -288,7 +288,9 @@ abstract contract AbstractZkBobPoolDecentralizedTest is AbstractZkBobPoolTestBas pool.proveTreeUpdate(commitment, _randProof(), _randFR()); } - function _prepareRandomDirectDeposits(uint256 offset) + function _prepareRandomDirectDeposits( + uint256 offset + ) internal returns (uint256[] memory indices, uint256 commitment, uint256[8] memory proof) {