From 19b3c86c9668df0cafce0fd5f7ce3c80e0221b73 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Mon, 24 Oct 2022 11:09:35 -0300 Subject: [PATCH] Support for in-snark transfer limits --- src/interfaces/ITransferVerifier.sol | 2 +- src/zkbob/ZkBobPool.sol | 17 ++++++++++++++++- src/zkbob/utils/CustomABIDecoder.sol | 9 ++++++++- src/zkbob/utils/Parameters.sol | 14 +++++++++++++- src/zkbob/utils/ZkBobAccounting.sol | 2 +- test/mocks/TransferVerifierMock.sol | 2 +- test/zkbob/ZkBobPool.t.sol | 27 +++++++++++++++++++++++---- 7 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/interfaces/ITransferVerifier.sol b/src/interfaces/ITransferVerifier.sol index cc61d8c..6f1b4f9 100644 --- a/src/interfaces/ITransferVerifier.sol +++ b/src/interfaces/ITransferVerifier.sol @@ -3,5 +3,5 @@ pragma solidity 0.8.15; interface ITransferVerifier { - function verifyProof(uint256[5] memory input, uint256[8] memory p) external view returns (bool); + function verifyProof(uint256[9] memory input, uint256[8] memory p) external view returns (bool); } diff --git a/src/zkbob/ZkBobPool.sol b/src/zkbob/ZkBobPool.sol index edbe739..f1392b6 100644 --- a/src/zkbob/ZkBobPool.sol +++ b/src/zkbob/ZkBobPool.sol @@ -28,6 +28,10 @@ contract ZkBobPool is EIP1967Admin, Ownable, Parameters, ZkBobAccounting { uint256 internal constant MAX_POOL_ID = 0xffffff; uint256 internal constant TOKEN_DENOMINATOR = 1_000_000_000; + uint256 internal constant DAILY_TURNOVER_LIMIT = 100_000 gwei; + uint256 internal constant TRANSFER_LIMIT = 20_000 gwei; + uint256 internal constant MIN_OUT_NOTE_LIMIT = 0.1 gwei; + uint256 public immutable pool_id; ITransferVerifier public immutable transfer_verifier; ITreeVerifier public immutable tree_verifier; @@ -162,7 +166,18 @@ contract ZkBobPool is EIP1967Admin, Ownable, Parameters, ZkBobAccounting { require(nullifiers[nullifier] == 0, "ZkBobPool: doublespend detected"); require(_transfer_index() <= _pool_index, "ZkBobPool: transfer index out of bounds"); - require(transfer_verifier.verifyProof(_transfer_pub(), _transfer_proof()), "ZkBobPool: bad transfer proof"); + uint256 day = _transfer_day(); + // to avoid unexpected reverts on the day boundary, the proof is considered valid until 00:05 of the next day + require( + day == block.timestamp / 1 days || day == (block.timestamp - 5 minutes) / 1 days, + "ZkBobPool: transfer proof expired" + ); + require( + transfer_verifier.verifyProof( + _transfer_pub(DAILY_TURNOVER_LIMIT, TRANSFER_LIMIT, MIN_OUT_NOTE_LIMIT), _transfer_proof() + ), + "ZkBobPool: bad transfer proof" + ); require( tree_verifier.verifyProof(_tree_pub(roots[_pool_index]), _tree_proof()), "ZkBobPool: bad tree proof" ); diff --git a/src/zkbob/utils/CustomABIDecoder.sol b/src/zkbob/utils/CustomABIDecoder.sol index 71e2c0a..69e51ef 100644 --- a/src/zkbob/utils/CustomABIDecoder.sol +++ b/src/zkbob/utils/CustomABIDecoder.sol @@ -45,7 +45,14 @@ contract CustomABIDecoder { r = int64(uint64(_loaduint256(transfer_token_amount_pos + transfer_token_amount_size - uint256_size))); } - uint256 constant transfer_proof_pos = transfer_token_amount_pos + transfer_token_amount_size; + uint256 constant transfer_day_pos = transfer_token_amount_pos + transfer_token_amount_size; + uint256 constant transfer_day_size = 3; + + function _transfer_day() internal pure returns (uint256 r) { + r = uint256(uint24(_loaduint256(transfer_day_pos + transfer_day_size - uint256_size))); + } + + uint256 constant transfer_proof_pos = transfer_day_pos + transfer_day_size; uint256 constant transfer_proof_size = 256; function _transfer_proof() internal pure returns (uint256[8] calldata r) { diff --git a/src/zkbob/utils/Parameters.sol b/src/zkbob/utils/Parameters.sol index a27f2fa..b6952eb 100644 --- a/src/zkbob/utils/Parameters.sol +++ b/src/zkbob/utils/Parameters.sol @@ -12,12 +12,24 @@ abstract contract Parameters is CustomABIDecoder { function _root() internal view virtual returns (uint256); function _pool_id() internal view virtual returns (uint256); - function _transfer_pub() internal view returns (uint256[5] memory r) { + function _transfer_pub( + uint256 _daily_limit, + uint256 _transfer_limit, + uint256 _out_note_min_limit + ) + internal + view + returns (uint256[9] memory r) + { r[0] = _root(); r[1] = _transfer_nullifier(); r[2] = _transfer_out_commit(); r[3] = _transfer_delta() + (_pool_id() << (transfer_delta_size * 8)); r[4] = uint256(keccak256(_memo_data())) % R; + r[5] = _transfer_day(); + r[6] = _daily_limit; + r[7] = _transfer_limit; + r[8] = _out_note_min_limit; } function _tree_pub(uint256 _root_before) internal view returns (uint256[3] memory r) { diff --git a/src/zkbob/utils/ZkBobAccounting.sol b/src/zkbob/utils/ZkBobAccounting.sol index 2ec0c2d..b83a23f 100644 --- a/src/zkbob/utils/ZkBobAccounting.sol +++ b/src/zkbob/utils/ZkBobAccounting.sol @@ -58,7 +58,7 @@ contract ZkBobAccounting { // max cap on the daily withdrawal sum (granularity of 1e9) // max possible cap - type(uint32).max * 1e9 zkBOB units ~= 4.3e9 BOB uint32 dailyWithdrawalCap; - // max cap on the daily deposits sum for single user (granularity of 1e9) + // max cap on the daily deposits sum for a single user (granularity of 1e9) // max possible cap - type(uint32).max * 1e9 zkBOB units ~= 4.3e9 BOB uint32 dailyUserDepositCap; // max cap on single deposit (granularity of 1e9) diff --git a/test/mocks/TransferVerifierMock.sol b/test/mocks/TransferVerifierMock.sol index a459c4a..61c5e52 100644 --- a/test/mocks/TransferVerifierMock.sol +++ b/test/mocks/TransferVerifierMock.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.15; import "../../src/interfaces/ITransferVerifier.sol"; contract TransferVerifierMock is ITransferVerifier { - function verifyProof(uint256[5] memory, uint256[8] memory) external pure returns (bool) { + function verifyProof(uint256[9] memory, uint256[8] memory) external pure returns (bool) { return true; } } diff --git a/test/zkbob/ZkBobPool.t.sol b/test/zkbob/ZkBobPool.t.sol index 1136767..9bf0b29 100644 --- a/test/zkbob/ZkBobPool.t.sol +++ b/test/zkbob/ZkBobPool.t.sol @@ -198,7 +198,13 @@ contract ZkBobPoolTest is Test { (uint8 v, bytes32 r, bytes32 s) = _signSaltedPermit(pk1, user1, address(pool), _amount + 0.01 ether, bob.nonces(user1), expiry, nullifier); bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, nullifier, _randFR(), uint48(0), uint112(0), int64(int256(_amount / 1 gwei)) + ZkBobPool.transact.selector, + nullifier, + _randFR(), + uint48(0), + uint112(0), + int64(int256(_amount / 1 gwei)), + uint24(block.timestamp / 1 days) ); for (uint256 i = 0; i < 17; i++) { data = abi.encodePacked(data, _randFR()); @@ -220,7 +226,13 @@ contract ZkBobPoolTest is Test { 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(int256(_amount / 1 gwei)) + ZkBobPool.transact.selector, + nullifier, + _randFR(), + uint48(0), + uint112(0), + int64(int256(_amount / 1 gwei)), + uint24(block.timestamp / 1 days) ); for (uint256 i = 0; i < 17; i++) { data = abi.encodePacked(data, _randFR()); @@ -238,7 +250,8 @@ contract ZkBobPoolTest is Test { _randFR(), uint48(0), uint112(0), - int64(-int256((_amount + 0.01 ether) / 1 gwei)) + int64(-int256((_amount + 0.01 ether) / 1 gwei)), + uint24(block.timestamp / 1 days) ); for (uint256 i = 0; i < 17; i++) { data = abi.encodePacked(data, _randFR()); @@ -257,7 +270,13 @@ contract ZkBobPoolTest is Test { function _encodeTransfer() internal returns (bytes memory) { bytes memory data = abi.encodePacked( - ZkBobPool.transact.selector, _randFR(), _randFR(), uint48(0), uint112(0), int64(-0.01 ether / 1 gwei) + ZkBobPool.transact.selector, + _randFR(), + _randFR(), + uint48(0), + uint112(0), + int64(-0.01 ether / 1 gwei), + uint24(block.timestamp / 1 days) ); for (uint256 i = 0; i < 17; i++) { data = abi.encodePacked(data, _randFR());