Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for in-snark transfer limits #23

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/interfaces/ITransferVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
17 changes: 16 additions & 1 deletion src/zkbob/ZkBobPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"
);
Expand Down
9 changes: 8 additions & 1 deletion src/zkbob/utils/CustomABIDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
14 changes: 13 additions & 1 deletion src/zkbob/utils/Parameters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/zkbob/utils/ZkBobAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion test/mocks/TransferVerifierMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
27 changes: 23 additions & 4 deletions test/zkbob/ZkBobPool.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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());
Expand All @@ -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());
Expand All @@ -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());
Expand Down