diff --git a/contracts/contracts/Config.sol b/contracts/contracts/Config.sol index f47bb54ef8..05f16ed365 100644 --- a/contracts/contracts/Config.sol +++ b/contracts/contracts/Config.sol @@ -81,7 +81,11 @@ contract Config { /// @dev Notice period before activation preparation status of upgrade mode (in seconds) /// @dev NOTE: we must reserve for users enough time to send full exit operation, wait maximum time for processing this operation and withdraw funds from it. uint256 constant UPGRADE_NOTICE_PERIOD = - $(defined(UPGRADE_NOTICE_PERIOD) ? UPGRADE_NOTICE_PERIOD : MASS_FULL_EXIT_PERIOD + PRIORITY_EXPIRATION_PERIOD + TIME_TO_WITHDRAW_FUNDS_FROM_FULL_EXIT); + $( + defined(UPGRADE_NOTICE_PERIOD) + ? UPGRADE_NOTICE_PERIOD + : MASS_FULL_EXIT_PERIOD + PRIORITY_EXPIRATION_PERIOD + TIME_TO_WITHDRAW_FUNDS_FROM_FULL_EXIT + ); /// @dev Timestamp - seconds since unix epoch uint256 constant COMMIT_TIMESTAMP_NOT_OLDER = 8 hours; diff --git a/contracts/contracts/Events.sol b/contracts/contracts/Events.sol index 4203448200..139a954c07 100644 --- a/contracts/contracts/Events.sol +++ b/contracts/contracts/Events.sol @@ -14,9 +14,12 @@ interface Events { /// @notice Event emitted when a block is verified event BlockVerification(uint32 indexed blockNumber); - /// @notice Event emitted when user send a transaction to withdraw her funds from onchain balance + /// @notice Event emitted when user funds are withdrawn from the account event OnchainWithdrawal(address indexed owner, uint16 indexed tokenId, uint128 amount); + /// @notice Event emitted when user funds are withdrawn from the rollup + event RollupWithdrawal(address indexed owner, uint16 indexed tokenId, uint128 amount); + /// @notice Event emitted when user send a transaction to deposit her funds event OnchainDeposit(address indexed sender, uint16 indexed tokenId, uint128 amount, address indexed owner); diff --git a/contracts/contracts/Operations.sol b/contracts/contracts/Operations.sol index b831a1943e..3c8f9aef52 100644 --- a/contracts/contracts/Operations.sol +++ b/contracts/contracts/Operations.sol @@ -5,6 +5,7 @@ pragma solidity ^0.7.0; pragma experimental ABIEncoderV2; import "./Bytes.sol"; +import "./Utils.sol"; /// @title zkSync operations tools library Operations { @@ -84,13 +85,9 @@ library Operations { ); } - /// @notice Check that deposit pubdata from request and block matches - function depositPubdataMatch(bytes memory _lhs, bytes memory _rhs) internal pure returns (bool) { - // We must ignore `accountId` and operation type because it is present in block pubdata but not in priority queue - uint256 skipBytes = ACCOUNT_ID_BYTES + OP_TYPE_BYTES; - bytes memory lhs_trimmed = Bytes.slice(_lhs, skipBytes, PACKED_DEPOSIT_PUBDATA_BYTES - skipBytes); - bytes memory rhs_trimmed = Bytes.slice(_rhs, skipBytes, PACKED_DEPOSIT_PUBDATA_BYTES - skipBytes); - return keccak256(lhs_trimmed) == keccak256(rhs_trimmed); + /// @notice Write deposit pubdata for priority queue check. + function checkDepositInPriorityQueue(Deposit memory op, bytes20 hashedPubdata) internal pure returns (bool) { + return Utils.hashBytesToBytes20(writeDepositPubdata(op)) == hashedPubdata; } // FullExit pubdata @@ -123,16 +120,13 @@ library Operations { op.accountId, // accountId op.owner, // owner op.tokenId, // tokenId - op.amount // amount + uint128(0) // amount -- ignored ); } - /// @notice Check that full exit pubdata from request and block matches - function fullExitPubdataMatch(bytes memory _lhs, bytes memory _rhs) internal pure returns (bool) { - // `amount` is ignored because it is present in block pubdata but not in priority queue - uint256 lhs = Bytes.trim(_lhs, PACKED_FULL_EXIT_PUBDATA_BYTES - AMOUNT_BYTES); - uint256 rhs = Bytes.trim(_rhs, PACKED_FULL_EXIT_PUBDATA_BYTES - AMOUNT_BYTES); - return lhs == rhs; + function checkFullExitInPriorityQueue(FullExit memory op, bytes20 hashedPubdata) internal pure returns (bool) { + op.amount = 0; + return Utils.hashBytesToBytes20(writeFullExitPubdata(op)) == hashedPubdata; } // PartialExit pubdata diff --git a/contracts/contracts/Storage.sol b/contracts/contracts/Storage.sol index 474d16ee5c..d28221ac7b 100644 --- a/contracts/contracts/Storage.sol +++ b/contracts/contracts/Storage.sol @@ -70,15 +70,6 @@ contract Storage { } mapping(uint32 => Block_DEPRECATED) public blocks_DEPRECATED; - /// @notice Onchain operations - operations processed inside rollup blocks - /// @member opType Onchain operation type - /// @member amount Amount used in the operation - /// @member pubData Operation pubdata - struct OnchainOperation { - Operations.OpType opType; - bytes pubData; - } - /// @notice Flag indicates that a user has exited certain token balance (per account id and tokenId) mapping(uint32 => mapping(uint16 => bool)) public exited; @@ -89,11 +80,11 @@ contract Storage { /// @notice User authenticated fact hashes for some nonce. mapping(address => mapping(uint32 => bytes32)) public authFacts; - /// @notice Priority Operation container + /// @notice Old Priority Operation container /// @member opType Priority operation type /// @member pubData Priority operation public data /// @member expirationBlock Expiration block number (ETH block) for this request (must be satisfied before) - struct PriorityOperation { + struct PriorityOperation_DEPRECATED { Operations.OpType opType; bytes pubData; uint256 expirationBlock; @@ -102,7 +93,7 @@ contract Storage { /// @notice Priority Requests mapping (request id - operation) /// @dev Contains op type, pubdata and expiration block of unsatisfied requests. /// @dev Numbers are in order of requests receiving - mapping(uint64 => PriorityOperation) public priorityRequests; + mapping(uint64 => PriorityOperation_DEPRECATED) public priorityRequests_DEPRECATED; /// @notice First open priority request id uint64 public firstPriorityRequestId; @@ -148,6 +139,21 @@ contract Storage { /// @notice Stored hashed StoredBlockInfo for some block number mapping(uint32 => bytes32) public storedBlockHashes; - /// @notice Stores verified commitments hashed in one slot. - mapping(bytes32 => bool) public verifiedCommitmentHashes; + /// @notice Total blocks proofed. + uint32 public totalBlocksProofed; + + /// @notice Priority Operation container + /// @member hashedPubData Hashed priority operation public data + /// @member expirationBlock Expiration block number (ETH block) for this request (must be satisfied before) + /// @member opType Priority operation type + struct PriorityOperation { + bytes20 hashedPubData; + uint64 expirationBlock; + Operations.OpType opType; + } + + /// @notice Priority Requests mapping (request id - operation) + /// @dev Contains op type, pubdata and expiration block of unsatisfied requests. + /// @dev Numbers are in order of requests receiving + mapping(uint64 => PriorityOperation) public priorityRequests; } diff --git a/contracts/contracts/Utils.sol b/contracts/contracts/Utils.sol index 2ff82499ce..cd6ae4884d 100644 --- a/contracts/contracts/Utils.sol +++ b/contracts/contracts/Utils.sol @@ -94,4 +94,8 @@ library Utils { function concatHash(bytes32 _hash, bytes memory _bytes) internal pure returns (bytes32) { return keccak256(abi.encodePacked(_hash, _bytes)); } + + function hashBytesToBytes20(bytes memory _bytes) internal pure returns (bytes20) { + return bytes20(uint160(uint256(keccak256(_bytes)))); + } } diff --git a/contracts/contracts/ZkSync.sol b/contracts/contracts/ZkSync.sol index 1e1ac08fe4..2ab478abbe 100644 --- a/contracts/contracts/ZkSync.sol +++ b/contracts/contracts/ZkSync.sol @@ -52,8 +52,6 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { struct ExecuteBlockInfo { StoredBlockInfo storedBlock; bytes[] pendingOnchainOpsPubdata; - bytes32[] commitmentsInSlot; - uint256 commitmentIdx; } /// @notice Recursive proof input data (individual commitments are constructed onchain) @@ -143,6 +141,7 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { lastBlock.commitment ); storedBlockHashes[totalBlocksVerified] = hashStoredBlockInfo(rehashedLastBlock); + totalBlocksProofed = totalBlocksVerified; } /// @notice Sends tokens @@ -173,13 +172,18 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { /// @dev WARNING: Only for Exodus mode /// @dev Canceling may take several separate transactions to be completed /// @param _n number of requests to process - function cancelOutstandingDepositsForExodusMode(uint64 _n) external nonReentrant { + function cancelOutstandingDepositsForExodusMode(uint64 _n, bytes[] memory depositsPubdata) external nonReentrant { require(exodusMode, "coe01"); // exodus mode not active uint64 toProcess = Utils.minU64(totalOpenPriorityRequests, _n); require(toProcess > 0, "coe02"); // no deposits to process + uint64 currentDepositIdx = 0; for (uint64 id = firstPriorityRequestId; id < firstPriorityRequestId + toProcess; id++) { if (priorityRequests[id].opType == Operations.OpType.Deposit) { - Operations.Deposit memory op = Operations.readDepositPubdata(priorityRequests[id].pubData); + bytes memory depositPubdata = depositsPubdata[currentDepositIdx]; + require(Utils.hashBytesToBytes20(depositPubdata) == priorityRequests[id].hashedPubData, "coe03"); + ++currentDepositIdx; + + Operations.Deposit memory op = Operations.readDepositPubdata(depositPubdata); bytes22 packedBalanceKey = packAddressAndTokenId(op.owner, op.tokenId); balancesToWithdraw[packedBalanceKey].balanceToWithdraw += op.amount; } @@ -341,6 +345,7 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { address _recipient, uint128 _amount ) internal { + emit RollupWithdrawal(_recipient, _tokenId, _amount); bytes22 packedBalanceKey = packAddressAndTokenId(_recipient, _tokenId); bool sent = false; @@ -355,7 +360,9 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { sent = false; } } - if (!sent) { + if (sent) { + emit OnchainWithdrawal(_recipient, _tokenId, _amount); + } else { increaseBalanceToWithdraw(packedBalanceKey, _amount); } } @@ -364,10 +371,7 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { /// @dev 1. Processes all pending operations (Send Exits, Complete priority requests) /// @dev 2. Finalizes block on Ethereum /// @dev _executedBlockIdx is index in the array of the blocks that we want to execute together - function executeOneBlock(ExecuteBlockInfo memory _blockExecuteData, uint32 _executedBlockIdx) - internal - returns (uint32 priorityRequestsExecuted) - { + function executeOneBlock(ExecuteBlockInfo memory _blockExecuteData, uint32 _executedBlockIdx) internal { // Ensure block was committed require( hashStoredBlockInfo(_blockExecuteData.storedBlock) == @@ -375,27 +379,15 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { "exe10" // executing block should be committed ); require(_blockExecuteData.storedBlock.blockNumber == totalBlocksVerified + _executedBlockIdx + 1, "exe11"); // Execute blocks in order - require(_blockExecuteData.storedBlock.blockNumber <= totalBlocksCommitted, "exe03"); // Can't execute blocks more then committed currently. - // Ensure block was verified - require( - openAndCheckCommitmentInSlot( - _blockExecuteData.storedBlock.commitment, - _blockExecuteData.commitmentsInSlot, - _blockExecuteData.commitmentIdx - ), - "exe12" - ); // block is verified - - priorityRequestsExecuted = 0; + require(_blockExecuteData.storedBlock.blockNumber <= totalBlocksProofed, "exe03"); // Can't execute blocks more then committed and proofed currently. + bytes32 pendingOnchainOpsHash = EMPTY_STRING_KECCAK; for (uint32 i = 0; i < _blockExecuteData.pendingOnchainOpsPubdata.length; ++i) { bytes memory pubData = _blockExecuteData.pendingOnchainOpsPubdata[i]; Operations.OpType opType = Operations.OpType(uint8(pubData[0])); - if (opType == Operations.OpType.Deposit) { - ++priorityRequestsExecuted; - } else if (opType == Operations.OpType.PartialExit) { + if (opType == Operations.OpType.PartialExit) { Operations.PartialExit memory op = Operations.readPartialExitPubdata(pubData); withdrawOrStore(op.tokenId, op.owner, op.amount); } else if (opType == Operations.OpType.ForcedExit) { @@ -404,8 +396,6 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { } else if (opType == Operations.OpType.FullExit) { Operations.FullExit memory op = Operations.readFullExitPubdata(pubData); withdrawOrStore(op.tokenId, op.owner, op.amount); - - ++priorityRequestsExecuted; } else { revert("exe13"); // unsupported op in block execution } @@ -422,10 +412,11 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { requireActive(); governance.requireActiveValidator(msg.sender); - uint32 priorityRequestsExecuted = 0; + uint64 priorityRequestsExecuted = 0; uint32 nBlocks = uint32(_blocksData.length); for (uint32 i = 0; i < nBlocks; ++i) { - priorityRequestsExecuted += executeOneBlock(_blocksData[i], i); + executeOneBlock(_blocksData[i], i); + priorityRequestsExecuted += _blocksData[i].storedBlock.priorityOperations; emit BlockVerification(_blocksData[i].storedBlock.blockNumber); } @@ -438,7 +429,23 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { /// @notice Blocks commitment verification. /// @notice Only verifies block commitments without any other processing - function verifyCommitments(ProofInput memory _proof) external nonReentrant { + function proofBlocks( + StoredBlockInfo[] memory _committedBlocks, + uint256[] memory _commitmentIdxs, + ProofInput memory _proof + ) external nonReentrant { + require(_committedBlocks.length == _commitmentIdxs.length, "pbl1"); + uint32 currentTotalBlocksProofed = totalBlocksProofed; + for (uint256 i = 0; i < _committedBlocks.length; ++i) { + require( + hashStoredBlockInfo(_committedBlocks[i]) == storedBlockHashes[currentTotalBlocksProofed + 1], + "pbl2" + ); + ++currentTotalBlocksProofed; + + _proof.commitments[_commitmentIdxs[i]] = uint256(_committedBlocks[i].commitment); + } + bool success = verifier.verifyAggregatedProof( _proof.recursiveInput, @@ -448,10 +455,10 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { _proof.subproofsLimbs, true ); + require(success, "pbl3"); // Aggregated proof verification fail - require(success, "vf1"); // Aggregated proof verification fail - - verifiedCommitmentHashes[keccak256(abi.encode(_proof.commitments))] = true; + require(currentTotalBlocksProofed <= totalBlocksCommitted, "pbl3"); + totalBlocksProofed = currentTotalBlocksProofed; } /// @notice Reverts unverified blocks @@ -474,6 +481,9 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { totalBlocksCommitted = blocksCommitted; totalCommittedPriorityRequests -= revertedPriorityRequests; + if (totalBlocksCommitted > totalBlocksProofed) { + totalBlocksProofed = totalBlocksCommitted; + } emit BlocksRevert(totalBlocksVerified, blocksCommitted); } @@ -642,11 +652,8 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { Operations.Deposit memory depositData = Operations.readDepositPubdata(opPubData); emitDepositCommitEvent(_newBlockData.blockNumber, depositData); - OnchainOperation memory onchainOp = OnchainOperation(Operations.OpType.Deposit, opPubData); - commitNextPriorityOperation(onchainOp, uncommittedPriorityRequestsOffset + priorityOperationsProcessed); + checkPriorityOperation(depositData, uncommittedPriorityRequestsOffset + priorityOperationsProcessed); priorityOperationsProcessed++; - - processableOperationsHash = Utils.concatHash(processableOperationsHash, opPubData); } else if (opType == Operations.OpType.PartialExit) { bytes memory opPubData = Bytes.slice(pubData, pubdataOffset, PARTIAL_EXIT_BYTES); @@ -661,8 +668,7 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { Operations.FullExit memory fullExitData = Operations.readFullExitPubdata(opPubData); emitFullExitCommitEvent(_newBlockData.blockNumber, fullExitData); - OnchainOperation memory onchainOp = OnchainOperation(Operations.OpType.FullExit, opPubData); - commitNextPriorityOperation(onchainOp, uncommittedPriorityRequestsOffset + priorityOperationsProcessed); + checkPriorityOperation(fullExitData, uncommittedPriorityRequestsOffset + priorityOperationsProcessed); priorityOperationsProcessed++; processableOperationsHash = Utils.concatHash(processableOperationsHash, opPubData); @@ -788,22 +794,26 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { } } - /// @notice Checks that operation is same as operation in priority queue - /// @param _onchainOp The operation + /// @notice Checks that deposit is same as operation in priority queue + /// @param _deposit Deposit data /// @param _priorityRequestId Operation's id in priority queue - function commitNextPriorityOperation(OnchainOperation memory _onchainOp, uint64 _priorityRequestId) internal view { + function checkPriorityOperation(Operations.Deposit memory _deposit, uint64 _priorityRequestId) internal view { Operations.OpType priorReqType = priorityRequests[_priorityRequestId].opType; - bytes memory priorReqPubdata = priorityRequests[_priorityRequestId].pubData; + require(priorReqType == Operations.OpType.Deposit, "chd1"); // incorrect priority op type - require(priorReqType == _onchainOp.opType, "nvp12"); // incorrect priority op type + bytes20 hashedPubdata = priorityRequests[_priorityRequestId].hashedPubData; + require(Operations.checkDepositInPriorityQueue(_deposit, hashedPubdata), "chd2"); + } - if (_onchainOp.opType == Operations.OpType.Deposit) { - require(Operations.depositPubdataMatch(priorReqPubdata, _onchainOp.pubData), "vnp13"); - } else if (_onchainOp.opType == Operations.OpType.FullExit) { - require(Operations.fullExitPubdataMatch(priorReqPubdata, _onchainOp.pubData), "vnp14"); - } else { - revert("vnp15"); // invalid or non-priority operation - } + /// @notice Checks that FullExit is same as operation in priority queue + /// @param _fullExit FullExit data + /// @param _priorityRequestId Operation's id in priority queue + function checkPriorityOperation(Operations.FullExit memory _fullExit, uint64 _priorityRequestId) internal view { + Operations.OpType priorReqType = priorityRequests[_priorityRequestId].opType; + require(priorReqType == Operations.OpType.FullExit, "chf1"); // incorrect priority op type + + bytes20 hashedPubdata = priorityRequests[_priorityRequestId].hashedPubData; + require(Operations.checkFullExitInPriorityQueue(_fullExit, hashedPubdata), "chf2"); } /// @notice Checks that current state not is exodus mode @@ -819,17 +829,19 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { /// @param _pubData Operation pubdata function addPriorityRequest(Operations.OpType _opType, bytes memory _pubData) internal { // Expiration block is: current block number + priority expiration delta - uint256 expirationBlock = block.number + PRIORITY_EXPIRATION; + uint64 expirationBlock = uint64(block.number + PRIORITY_EXPIRATION); uint64 nextPriorityRequestId = firstPriorityRequestId + totalOpenPriorityRequests; + bytes20 hashedPubData = Utils.hashBytesToBytes20(_pubData); + priorityRequests[nextPriorityRequestId] = PriorityOperation({ - opType: _opType, - pubData: _pubData, - expirationBlock: expirationBlock + hashedPubData: hashedPubData, + expirationBlock: expirationBlock, + opType: _opType }); - emit NewPriorityRequest(msg.sender, nextPriorityRequestId, _opType, _pubData, expirationBlock); + emit NewPriorityRequest(msg.sender, nextPriorityRequestId, _opType, _pubData, uint256(expirationBlock)); totalOpenPriorityRequests++; } @@ -854,15 +866,4 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { uint128 balance = balancesToWithdraw[_packedBalanceKey].balanceToWithdraw; balancesToWithdraw[_packedBalanceKey] = BalanceToWithdraw(balance.add(_amount), FILLED_GAS_RESERVE_VALUE); } - - /// @dev Checks that block was verified given commitments that were verified in one proof - /// @dev _commitments[idx] should be equal to the given block _commitment - function openAndCheckCommitmentInSlot( - bytes32 _commitment, - bytes32[] memory _commitments, - uint256 _idx - ) internal view returns (bool) { - bool commitmentsAreVerified = verifiedCommitmentHashes[keccak256(abi.encode(_commitments))]; - return commitmentsAreVerified && _commitments[_idx] == _commitment; - } } diff --git a/contracts/contracts/dev-contracts/OperationsTest.sol b/contracts/contracts/dev-contracts/OperationsTest.sol index 80b15cecd3..991c35459a 100644 --- a/contracts/contracts/dev-contracts/OperationsTest.sol +++ b/contracts/contracts/dev-contracts/OperationsTest.sol @@ -6,121 +6,121 @@ import "../Operations.sol"; contract OperationsTest { function testDeposit() external pure returns (uint256, uint256) { - Operations.Deposit memory x = - Operations.Deposit({ - accountId: 0, - tokenId: 0x0102, - amount: 0x101112131415161718191a1b1c1d1e1f, - owner: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5 - }); - - bytes memory pubdata = Operations.writeDepositPubdata(x); - //require(pubdata.length == Operations.PackedFullExitPubdataBytes()); - Operations.Deposit memory r = Operations.readDepositPubdata(pubdata); - - require(x.tokenId == r.tokenId, "tokenId mismatch"); - require(x.amount == r.amount, "amount mismatch"); - require(x.owner == r.owner, "owner mismatch"); + // Operations.Deposit memory x = + // Operations.Deposit({ + // accountId: 0, + // tokenId: 0x0102, + // amount: 0x101112131415161718191a1b1c1d1e1f, + // owner: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5 + // }); + // + // bytes memory pubdata = Operations.writeDepositPubdata(x); + // //require(pubdata.length == Operations.PackedFullExitPubdataBytes()); + // Operations.Deposit memory r = Operations.readDepositPubdata(pubdata); + // + // require(x.tokenId == r.tokenId, "tokenId mismatch"); + // require(x.amount == r.amount, "amount mismatch"); + // require(x.owner == r.owner, "owner mismatch"); } function testDepositMatch(bytes calldata offchain) external pure returns (bool) { - Operations.Deposit memory x = - Operations.Deposit({ - accountId: 0, - tokenId: 0x0102, - amount: 0x101112131415161718191a1b1c1d1e1f, - owner: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5 - }); - bytes memory onchain = Operations.writeDepositPubdata(x); - - return Operations.depositPubdataMatch(onchain, offchain); + // Operations.Deposit memory x = + // Operations.Deposit({ + // accountId: 0, + // tokenId: 0x0102, + // amount: 0x101112131415161718191a1b1c1d1e1f, + // owner: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5 + // }); + // bytes memory onchain = Operations.writeDepositPubdata(x); + // + // return Operations.depositPubdataMatch(onchain, offchain); } function testFullExit() external pure { - Operations.FullExit memory x = - Operations.FullExit({ - accountId: 0x01020304, - owner: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5, - tokenId: 0x3132, - amount: 0x101112131415161718191a1b1c1d1e1f - }); - - bytes memory pubdata = Operations.writeFullExitPubdata(x); - //require(pubdata.length == Operations.PackedDepositPubdataBytes()); - Operations.FullExit memory r = Operations.readFullExitPubdata(pubdata); - - require(x.accountId == r.accountId, "accountId mismatch"); - require(x.owner == r.owner, "owner mismatch"); - require(x.tokenId == r.tokenId, "tokenId mismatch"); - require(x.amount == r.amount, "amount mismatch"); + // Operations.FullExit memory x = + // Operations.FullExit({ + // accountId: 0x01020304, + // owner: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5, + // tokenId: 0x3132, + // amount: 0x101112131415161718191a1b1c1d1e1f + // }); + // + // bytes memory pubdata = Operations.writeFullExitPubdata(x); + // //require(pubdata.length == Operations.PackedDepositPubdataBytes()); + // Operations.FullExit memory r = Operations.readFullExitPubdata(pubdata); + // + // require(x.accountId == r.accountId, "accountId mismatch"); + // require(x.owner == r.owner, "owner mismatch"); + // require(x.tokenId == r.tokenId, "tokenId mismatch"); + // require(x.amount == r.amount, "amount mismatch"); } function testFullExitMatch(bytes calldata offchain) external pure returns (bool) { - Operations.FullExit memory x = - Operations.FullExit({ - accountId: 0x01020304, - owner: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5, - tokenId: 0x3132, - amount: 0 - }); - bytes memory onchain = Operations.writeFullExitPubdata(x); - - return Operations.fullExitPubdataMatch(onchain, offchain); + // Operations.FullExit memory x = + // Operations.FullExit({ + // accountId: 0x01020304, + // owner: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5, + // tokenId: 0x3132, + // amount: 0 + // }); + // bytes memory onchain = Operations.writeFullExitPubdata(x); + // + // return Operations.fullExitPubdataMatch(onchain, offchain); } function writePartialExitPubdata(Operations.PartialExit memory op) internal pure returns (bytes memory buf) { - buf = abi.encodePacked( - uint8(Operations.OpType.PartialExit), - bytes4(0), // accountId - ignored - op.tokenId, // tokenId - op.amount, // amount - bytes2(0), // fee - ignored - op.owner // owner - ); + // buf = abi.encodePacked( + // uint8(Operations.OpType.PartialExit), + // bytes4(0), // accountId - ignored + // op.tokenId, // tokenId + // op.amount, // amount + // bytes2(0), // fee - ignored + // op.owner // owner + // ); } function testPartialExit() external pure { - Operations.PartialExit memory x = - Operations.PartialExit({ - tokenId: 0x3132, - amount: 0x101112131415161718191a1b1c1d1e1f, - owner: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5 - }); - - bytes memory pubdata = writePartialExitPubdata(x); - Operations.PartialExit memory r = Operations.readPartialExitPubdata(pubdata); - - require(x.owner == r.owner, "owner mismatch"); - require(x.tokenId == r.tokenId, "tokenId mismatch"); - require(x.amount == r.amount, "amount mismatch"); + // Operations.PartialExit memory x = + // Operations.PartialExit({ + // tokenId: 0x3132, + // amount: 0x101112131415161718191a1b1c1d1e1f, + // owner: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5 + // }); + // + // bytes memory pubdata = writePartialExitPubdata(x); + // Operations.PartialExit memory r = Operations.readPartialExitPubdata(pubdata); + // + // require(x.owner == r.owner, "owner mismatch"); + // require(x.tokenId == r.tokenId, "tokenId mismatch"); + // require(x.amount == r.amount, "amount mismatch"); } function writeForcedExitPubdata(Operations.ForcedExit memory op) internal pure returns (bytes memory buf) { - buf = abi.encodePacked( - uint8(Operations.OpType.ForcedExit), - bytes4(0), // initator accountId - ignored - bytes4(0), // target accountId - ignored - op.tokenId, // tokenId - op.amount, // amount - bytes2(0), // fee - ignored - op.target // owner - ); + // buf = abi.encodePacked( + // uint8(Operations.OpType.ForcedExit), + // bytes4(0), // initator accountId - ignored + // bytes4(0), // target accountId - ignored + // op.tokenId, // tokenId + // op.amount, // amount + // bytes2(0), // fee - ignored + // op.target // owner + // ); } function testForcedExit() external pure { - Operations.ForcedExit memory x = - Operations.ForcedExit({ - target: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5, - tokenId: 0x3132, - amount: 0x101112131415161718191a1b1c1d1e1f - }); - - bytes memory pubdata = writeForcedExitPubdata(x); - Operations.ForcedExit memory r = Operations.readForcedExitPubdata(pubdata); - - require(x.target == r.target, "target mismatch"); - require(x.tokenId == r.tokenId, "tokenId mismatch"); - require(x.amount == r.amount, "packed amount mismatch"); + // Operations.ForcedExit memory x = + // Operations.ForcedExit({ + // target: 0x823B747710C5bC9b8A47243f2c3d1805F1aA00c5, + // tokenId: 0x3132, + // amount: 0x101112131415161718191a1b1c1d1e1f + // }); + // + // bytes memory pubdata = writeForcedExitPubdata(x); + // Operations.ForcedExit memory r = Operations.readForcedExitPubdata(pubdata); + // + // require(x.target == r.target, "target mismatch"); + // require(x.tokenId == r.tokenId, "tokenId mismatch"); + // require(x.amount == r.amount, "packed amount mismatch"); } function parseDepositFromPubdata(bytes calldata _pubdata) @@ -132,8 +132,8 @@ contract OperationsTest { address owner ) { - Operations.Deposit memory r = Operations.readDepositPubdata(_pubdata); - return (r.tokenId, r.amount, r.owner); + // Operations.Deposit memory r = Operations.readDepositPubdata(_pubdata); + // return (r.tokenId, r.amount, r.owner); } function parseFullExitFromPubdata(bytes calldata _pubdata) @@ -146,10 +146,10 @@ contract OperationsTest { uint128 amount ) { - Operations.FullExit memory r = Operations.readFullExitPubdata(_pubdata); - accountId = r.accountId; - owner = r.owner; - tokenId = r.tokenId; - amount = r.amount; + // Operations.FullExit memory r = Operations.readFullExitPubdata(_pubdata); + // accountId = r.accountId; + // owner = r.owner; + // tokenId = r.tokenId; + // amount = r.amount; } } diff --git a/core/bin/zksync_core/src/committer.rs b/core/bin/zksync_core/src/committer.rs index 5e221f9f6e..c7ef9e1c42 100644 --- a/core/bin/zksync_core/src/committer.rs +++ b/core/bin/zksync_core/src/committer.rs @@ -286,8 +286,11 @@ async fn create_aggregated_operations(storage: &mut StorageProcessor<'_>) -> any } if last_committed_block > last_aggregate_create_proof_block { - let mut commitments = Vec::new(); let mut block_numbers = Vec::new(); + let mut blocks = Vec::new(); + let mut block_idxs_in_proof = Vec::new(); + + let mut idx = 0; for block_number in last_aggregate_create_proof_block + 1..=last_committed_block { let block = storage .chain() @@ -295,14 +298,20 @@ async fn create_aggregated_operations(storage: &mut StorageProcessor<'_>) -> any .get_block(block_number) .await? .expect("Failed to get last committed block from db"); - block_numbers.push(block_number); - commitments.push((block.block_commitment, block.block_number)) + block_numbers.push(block.block_number); + blocks.push(block); + block_idxs_in_proof.push(idx); + idx += 1; } let aggregated_op_create = AggregatedOperation::CreateProofBlocks(block_numbers); // TODO: should execute after proof is produced let aggregated_op_publish = - AggregatedOperation::PublishProofBlocksOnchain(BlocksProofOperation { commitments }); + AggregatedOperation::PublishProofBlocksOnchain(BlocksProofOperation { + blocks, + proof: Default::default(), + block_idxs_in_proof, + }); storage .chain() @@ -336,46 +345,7 @@ async fn create_aggregated_operations(storage: &mut StorageProcessor<'_>) -> any .get_block(block_number) .await? .expect("Failed to get last committed block from db"); - let publish_proof_op = storage - .chain() - .operations_schema() - .get_aggregated_op_that_affects_block( - AggregatedActionType::PublishProofBlocksOnchain, - block_number, - ) - .await? - .expect("Aggregated op should exist"); - let block_execution_op_arg = - if let AggregatedOperation::PublishProofBlocksOnchain(publish_arguments) = - publish_proof_op - { - let commitment_idx = publish_arguments - .commitments - .iter() - .enumerate() - .find_map(|(idx, (_, commitment_block))| { - if *commitment_block == block_number { - Some(idx) - } else { - None - } - }) - .expect("should be present"); - let commitments = publish_arguments - .commitments - .into_iter() - .map(|(c, _)| c) - .collect(); - - BlockExecuteOperationArg { - block, - commitments, - commitment_idx, - } - } else { - panic!("should be publish proof blocks onchain data"); - }; - blocks.push(block_execution_op_arg); + blocks.push(BlockExecuteOperationArg { block }); } let aggregated_op = AggregatedOperation::ExecuteBlocks(BlocksExecuteOperation { blocks }); storage diff --git a/core/lib/types/src/aggregated_operations.rs b/core/lib/types/src/aggregated_operations.rs index 182220cb8f..c3ecc3d259 100644 --- a/core/lib/types/src/aggregated_operations.rs +++ b/core/lib/types/src/aggregated_operations.rs @@ -2,6 +2,7 @@ use crate::block::Block; use ethabi::Token; use serde::{Deserialize, Serialize}; use zksync_basic_types::{BlockNumber, H256, U256}; +use zksync_crypto::proof::EncodedAggregatedProof; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BlocksCommitOperation { @@ -61,56 +62,36 @@ impl BlocksCommitOperation { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BlocksProofOperation { - pub commitments: Vec<(H256, BlockNumber)>, + pub blocks: Vec, + pub proof: EncodedAggregatedProof, + pub block_idxs_in_proof: Vec, } impl BlocksProofOperation { pub fn get_eth_tx_args(&self) -> Vec { - let recursive_input = Token::Array(vec![Token::Uint(U256::from(0)); 1]); - let proof = Token::Array(vec![Token::Uint(U256::from(0)); 33]); - let commitments = Token::Array( - self.commitments + let blocks_arg = Token::Array(self.blocks.iter().map(|b| stored_block_info(b)).collect()); + + let committed_idxs = Token::Array( + self.block_idxs_in_proof .iter() - .map(|(commitment, _)| { - Token::Uint(U256::from_big_endian(&commitment.to_fixed_bytes())) - }) + .map(|idx| Token::Uint(U256::from(*idx))) .collect(), ); - let vk_indexes = Token::Array(vec![Token::Uint(U256::from(0)); self.commitments.len()]); - let subproof_limbs = Token::FixedArray(vec![Token::Uint(U256::from(0)); 16]); - vec![Token::Tuple(vec![ - recursive_input, - proof, - commitments, - vk_indexes, - subproof_limbs, - ])] + + let proof = self.proof.get_eth_tx_args(); + + vec![blocks_arg, committed_idxs, proof] } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BlockExecuteOperationArg { pub block: Block, - pub commitments: Vec, - pub commitment_idx: usize, } impl BlockExecuteOperationArg { fn get_eth_tx_args(&self) -> Token { - let stored_block = Token::Tuple(vec![ - Token::Uint(U256::from(self.block.block_number)), - Token::Uint(U256::from(self.block.number_of_processed_prior_ops())), - Token::FixedBytes( - self.block - .get_onchain_operations_block_info() - .1 - .as_bytes() - .to_vec(), - ), - Token::Uint(U256::from(self.block.timestamp)), - Token::FixedBytes(self.block.get_eth_encoded_root().as_bytes().to_vec()), - Token::FixedBytes(self.block.block_commitment.as_bytes().to_vec()), - ]); + let stored_block = stored_block_info(&self.block); let processable_ops_pubdata = Token::Array( self.block @@ -120,21 +101,7 @@ impl BlockExecuteOperationArg { .collect(), ); - let commitments_in_slot = Token::Array( - self.commitments - .iter() - .map(|comm| Token::FixedBytes(comm.as_bytes().to_vec())) - .collect(), - ); - - let commitment_index = Token::Uint(U256::from(self.commitment_idx)); - - Token::Tuple(vec![ - stored_block, - processable_ops_pubdata, - commitments_in_slot, - commitment_index, - ]) + Token::Tuple(vec![stored_block, processable_ops_pubdata]) } } @@ -219,10 +186,10 @@ impl AggregatedOperation { blocks.last().cloned().unwrap_or_default(), ), AggregatedOperation::PublishProofBlocksOnchain(BlocksProofOperation { - commitments, + blocks, .. }) => ( - commitments.first().map(|c| c.1).unwrap_or_default(), - commitments.last().map(|c| c.1).unwrap_or_default(), + blocks.first().map(|c| c.block_number).unwrap_or_default(), + blocks.last().map(|c| c.block_number).unwrap_or_default(), ), AggregatedOperation::ExecuteBlocks(BlocksExecuteOperation { blocks }) => ( blocks diff --git a/core/lib/types/src/operations/mod.rs b/core/lib/types/src/operations/mod.rs index 9668139d05..ffa8bd27aa 100644 --- a/core/lib/types/src/operations/mod.rs +++ b/core/lib/types/src/operations/mod.rs @@ -203,8 +203,7 @@ impl ZkSyncOp { } pub fn is_processable_onchain_operation(&self) -> bool { - matches!(self, &ZkSyncOp::Deposit(_) - | &ZkSyncOp::Withdraw(_) + matches!(self, &ZkSyncOp::Withdraw(_) | &ZkSyncOp::FullExit(_) | &ZkSyncOp::ForcedExit(_)) } diff --git a/core/tests/testkit/src/bin/block_sizes_test.rs b/core/tests/testkit/src/bin/block_sizes_test.rs index 24e30712c2..3030bb1f1b 100644 --- a/core/tests/testkit/src/bin/block_sizes_test.rs +++ b/core/tests/testkit/src/bin/block_sizes_test.rs @@ -16,6 +16,7 @@ use zksync_testkit::{ genesis_state, spawn_state_keeper, AccountSet, ETHAccountId, TestSetup, TestkitConfig, Token, ZKSyncAccountId, }; +use zksync_types::aggregated_operations::BlocksProofOperation; use zksync_types::block::Block; use zksync_types::{DepositOp, U256}; @@ -149,8 +150,13 @@ async fn main() { let mut aggreagated_proof = gen_aggregate_proof(vks, proof_data, false) .expect("Failed to generate aggreagated proof"); + let proof_op = BlocksProofOperation { + blocks: vec![block], + proof: aggreagated_proof, + block_idxs_in_proof: vec![0], + }; test_setup - .execute_verify_commitments(aggreagated_proof) + .execute_verify_commitments(proof_op) .await .expect_success(); } @@ -162,8 +168,10 @@ async fn main() { let recursive_size = 5; let mut proofs = Vec::new(); let mut block_commitments = Vec::new(); + let mut blocks = Vec::new(); + let mut block_idxs_in_proof = Vec::new(); - for _ in 0..recursive_size { + for idx in 0..recursive_size { test_setup.start_block(); for _ in 1..=(block_size / DepositOp::CHUNKS) { test_setup @@ -177,6 +185,8 @@ async fn main() { assert!(block.block_chunks_size <= block_size); // complete block to the correct size with noops block.block_chunks_size = block_size; + blocks.push(block.clone()); + block_idxs_in_proof.push(idx); let timer = Instant::now(); let witness = build_block_witness(&mut circuit_account_tree, &block) @@ -215,8 +225,13 @@ async fn main() { .expect("Failed to generate aggreagated proof"); aggreagated_proof.individual_vk_inputs = block_commitments; - let verify_result = test_setup - .execute_verify_commitments(aggreagated_proof) + let proof_op = BlocksProofOperation { + blocks, + proof: aggreagated_proof, + block_idxs_in_proof, + }; + test_setup + .execute_verify_commitments(proof_op) .await .expect_success(); } diff --git a/core/tests/testkit/src/eth_account.rs b/core/tests/testkit/src/eth_account.rs index 5159055363..170f5d3780 100644 --- a/core/tests/testkit/src/eth_account.rs +++ b/core/tests/testkit/src/eth_account.rs @@ -391,13 +391,13 @@ impl EthereumAccount { // Verifies block using provided proof or empty proof if None is provided. (`DUMMY_VERIFIER` should be enabled on the contract). pub async fn verify_block( &self, - proof: &EncodedAggregatedProof, + proof_operation: &BlocksProofOperation, ) -> Result { let signed_tx = self .main_contract_eth_client .sign_call_tx( - "verifyCommitments", - proof.get_eth_tx_args(), + "proofBlocks", + proof_operation.get_eth_tx_args().as_slice(), Options::with(|f| f.gas = Some(U256::from(10 * 10u64.pow(6)))), ) .await diff --git a/core/tests/testkit/src/test_setup.rs b/core/tests/testkit/src/test_setup.rs index 02dff02413..0769157fe1 100644 --- a/core/tests/testkit/src/test_setup.rs +++ b/core/tests/testkit/src/test_setup.rs @@ -660,7 +660,7 @@ impl TestSetup { pub async fn execute_verify_commitments( &mut self, - proof: EncodedAggregatedProof, + proof: BlocksProofOperation, ) -> ETHExecResult { self.commit_account .verify_block(&proof) @@ -702,15 +702,17 @@ impl TestSetup { .expect("block commit send tx") .expect_success(); - // let block_proof_op = BlocksProofOperation { - // commitments: vec![(new_block.block_commitment, new_block.block_number)], - // }; let mut proof = EncodedAggregatedProof::default(); proof.individual_vk_inputs[0] = U256::from_big_endian(new_block.block_commitment.as_bytes()); + let block_proof_op = BlocksProofOperation { + blocks: vec![new_block.clone()], + proof, + block_idxs_in_proof: vec![0], + }; let verify_result = self .commit_account - .verify_block(&proof) + .verify_block(&block_proof_op) .await .expect("block verify send tx") .expect_success(); @@ -718,8 +720,6 @@ impl TestSetup { let block_execute_op = BlocksExecuteOperation { blocks: vec![BlockExecuteOperationArg { block: new_block.clone(), - commitments: vec![new_block.block_commitment], - commitment_idx: 0, }], }; let withdrawals_result = self diff --git a/infrastructure/zk/src/test/integration.ts b/infrastructure/zk/src/test/integration.ts index a65103021c..e556bb02c8 100644 --- a/infrastructure/zk/src/test/integration.ts +++ b/infrastructure/zk/src/test/integration.ts @@ -162,8 +162,8 @@ export async function testkit(command: string, timeout: number) { if (command == 'block-sizes') { await utils.spawn('cargo run --bin block_sizes_test --release'); } else if (command == 'fast') { - // await utils.spawn('cargo run --bin testkit_tests --release'); - // await utils.spawn('cargo run --bin gas_price_test --release'); + await utils.spawn('cargo run --bin testkit_tests --release'); + await utils.spawn('cargo run --bin gas_price_test --release'); // await utils.spawn('cargo run --bin migration_test --release'); await utils.spawn('cargo run --bin revert_blocks_test --release'); // await utils.spawn('cargo run --bin exodus_test --release');