美文网首页
ZkSync链上合约

ZkSync链上合约

作者: 雪落无留痕 | 来源:发表于2021-04-17 16:24 被阅读0次

本文为对ZkSync链上合约代码的学习笔记。

合约部署

合约部署需要指定起始genesis状态根hash, 指定network governor

    /// @notice zkSync contract initialization. Can be external because Proxy contract intercepts illegal calls of this function.
    /// @param initializationParameters Encoded representation of initialization parameters:
    /// @dev _governanceAddress The address of Governance contract
    /// @dev _verifierAddress The address of Verifier contract
    /// @dev _genesisStateHash Genesis blocks (first block) state tree root hash
    function initialize(bytes calldata initializationParameters) external {
        initializeReentrancyGuard();

        (address _governanceAddress, address _verifierAddress, bytes32 _genesisStateHash) =
            abi.decode(initializationParameters, (address, address, bytes32));

        verifier = Verifier(_verifierAddress);      // 验证者合约
        governance = Governance(_governanceAddress);  //治理合约

        // We need initial state hash because it is used in the commitment of the next block
        StoredBlockInfo memory storedBlockZero =
            StoredBlockInfo(0, 0, EMPTY_STRING_KECCAK, 0, _genesisStateHash, bytes32(0));

        storedBlockHashes[0] = hashStoredBlockInfo(storedBlockZero); // 存储创世块的hash值
    }

其中 storedBlockHashes 是个map映射,主要存储区块状态的hash值。

  /// @dev Stored hashed StoredBlockInfo for some block number
    mapping(uint32 => bytes32) internal storedBlockHashes;

治理

网络治理由ZKSync中的networkGovernor合约实现,它能够:

  • 改变验证者;

  • 添加新的token;

  • 移植新的合约;

抗审查

ZKSync利用 Priority queueExodus mode机制进行抗审查,保证资金的可取回。

存款

用户可通过下方式完成存款:

  • 发送ETH到智能合约中;

  • 调用depositERC20()函数

存款创建一个 deposit priority request 请求,并发出 NewPriorityRequest(opType, pubData, expirationBlock)事件,通知验证者必须将该请求包含在下一个块中。

    /// @notice Saves priority request in storage
    /// @dev Calculates expiration block for request, store this request and emit NewPriorityRequest event
    /// @param _opType Rollup operation type
    /// @param _pubData Operation pubdata
    function addPriorityRequest(Operations.OpType _opType, bytes memory _pubData) internal {
        // Expiration block is: current block number + priority expiration delta
        uint64 expirationBlock = uint64(block.number + PRIORITY_EXPIRATION);

        uint64 nextPriorityRequestId = firstPriorityRequestId + totalOpenPriorityRequests;

        bytes20 hashedPubData = Utils.hashBytesToBytes20(_pubData);
        
        // 添加到优化级队列中
        priorityRequests[nextPriorityRequestId] = PriorityOperation({
            hashedPubData: hashedPubData,
            expirationBlock: expirationBlock,
            opType: _opType
        });
        
        // 发出事件
        emit NewPriorityRequest(msg.sender, nextPriorityRequestId, _opType, _pubData, uint256(expirationBlock));

        totalOpenPriorityRequests++;
    }

priorityRequests 是个map映射,存储优先队列中的操作。

/// @dev 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) internal priorityRequests;

当验证者提交的块中包含电路操作deposit的时候,创建deposit onchain operationpriority request的验证保持一致。

    /// @notice Checks that deposit is same as operation in priority queue
    /// @param _deposit Deposit data
    /// @param _priorityRequestId Operation's id in priority queue
    function checkPriorityOperation(Operations.Deposit memory _deposit, uint64 _priorityRequestId) internal view {
        Operations.OpType priorReqType = priorityRequests[_priorityRequestId].opType;
        require(priorReqType == Operations.OpType.Deposit, "H"); // incorrect priority op type

        bytes20 hashedPubdata = priorityRequests[_priorityRequestId].hashedPubData;
        require(Operations.checkDepositInPriorityQueue(_deposit, hashedPubdata), "I");
    }

若合约进行 Exodus模式,块未验证,块中Deposit priority requesst请求中的资金返还给用户root-chain balance, 保证其可取回。

取款

Partial withdrawl

当块中包括partial_exit电路操作的时候,创建withdraw onchain operation, 若块验证通过,withdraw onchain operation中的资金返回给用户的root-chain balance.

    function executeOneBlock(ExecuteBlockInfo memory _blockExecuteData, uint32 _executedBlockIdx) internal {
        // Ensure block was committed
        require(
            hashStoredBlockInfo(_blockExecuteData.storedBlock) ==
                storedBlockHashes[_blockExecuteData.storedBlock.blockNumber],
            "exe10" // executing block should be committed
        );
        require(_blockExecuteData.storedBlock.blockNumber == totalBlocksExecuted + _executedBlockIdx + 1, "k"); // Execute blocks in order

        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.PartialExit) {
                Operations.PartialExit memory op = Operations.readPartialExitPubdata(pubData);
                withdrawOrStore(op.tokenId, op.owner, op.amount);  //Partial exit 取款
            } else if (opType == Operations.OpType.ForcedExit) {
                Operations.ForcedExit memory op = Operations.readForcedExitPubdata(pubData);
                withdrawOrStore(op.tokenId, op.target, op.amount); //ForcedExit 取款
            } else if (opType == Operations.OpType.FullExit) {
                Operations.FullExit memory op = Operations.readFullExitPubdata(pubData);
                withdrawOrStore(op.tokenId, op.owner, op.amount);    // FullExit 取款
            } else {
                revert("l"); // unsupported op in block execution
            }

            pendingOnchainOpsHash = Utils.concatHash(pendingOnchainOpsHash, pubData);
        }
        require(pendingOnchainOpsHash == _blockExecuteData.storedBlock.pendingOnchainOperationsHash, "m"); // incorrect onchain ops executed
    }

用户可以从root-chain balance 通过调用 withdrawETHwithdrawERC20取回资金。

    function withdrawETH(uint128 _amount) external nonReentrant {
        registerWithdrawal(0, _amount, msg.sender);
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "D"); // ETH withdraw failed
    }

Full exit

用户可以发起Full exit操作,当它认为他的交易被验证者审查的时候。

用户发起交易,调用ZKSync合约的requestFullExit()函数,其创建full exit priority request请求,并发出NewPriorityRequest(serialId, opType, pubData,expirationBlock)事件通知验证者必须将其包括在下一个块中。

    function requestFullExit(uint32 _accountId, address _token) public nonReentrant {
        requireActive();
        require(_accountId <= MAX_ACCOUNT_ID, "e");

        uint16 tokenId;
        if (_token == address(0)) {
            tokenId = 0;
        } else {
            tokenId = governance.validateTokenAddress(_token);
        }

        // Priority Queue request
        Operations.FullExit memory op =
            Operations.FullExit({
                accountId: _accountId,
                owner: msg.sender,
                tokenId: tokenId,
                amount: 0 // unknown at this point
            });
        bytes memory pubData = Operations.writeFullExitPubdataForPriorityQueue(op);
        addPriorityRequest(Operations.OpType.FullExit, pubData);

        // User must fill storage slot of balancesToWithdraw(msg.sender, tokenId) with nonzero value
        // In this case operator should just overwrite this slot during confirming withdrawal
        bytes22 packedBalanceKey = packAddressAndTokenId(msg.sender, tokenId);
        pendingBalances[packedBalanceKey].gasReserveValue = FILLED_GAS_RESERVE_VALUE;
    }

当验证者提交的块中包括full_exit操作的时候,创建对应的withdraw onchain operation, 验证其和priority request中的请求保持一致。若块验证通过,withdraw onchain operation中的资金返回给用户的root-chain balance.

若ZKSync合约进入Exodus模式,withdraw onchain operationfulll exit priority requests直接丢弃。

Block commitment

只有验证者可以提交一个块。已提交但未验证的块由MAX_UNVERIFIED_BLOCKS常量限制,当验证失败时候,保证有充足的gas 来 revert 所有提交的块。

    //批量提交多个块
    function commitBlocks(StoredBlockInfo memory _lastCommittedBlockData, CommitBlockInfo[] memory _newBlocksData)
        external
        nonReentrant
    {
        requireActive();
        governance.requireActiveValidator(msg.sender);  // 只有验证者可以提交
        // Check that we commit blocks after last committed block
        require(storedBlockHashes[totalBlocksCommitted] == hashStoredBlockInfo(_lastCommittedBlockData), "i"); // incorrect previous block data

        for (uint32 i = 0; i < _newBlocksData.length; ++i) {
            // 对每个块进行验证
            _lastCommittedBlockData = commitOneBlock(_lastCommittedBlockData, _newBlocksData[i]);  

            totalCommittedPriorityRequests += _lastCommittedBlockData.priorityOperations;
            storedBlockHashes[_lastCommittedBlockData.blockNumber] = hashStoredBlockInfo(_lastCommittedBlockData);

            emit BlockCommit(_lastCommittedBlockData.blockNumber);
        }

        totalBlocksCommitted += uint32(_newBlocksData.length);

        require(totalCommittedPriorityRequests <= totalOpenPriorityRequests, "j");
    }

Block verification

任何人都可以验证已提交的块。

    function proveBlocks(StoredBlockInfo[] memory _committedBlocks, ProofInput memory _proof) external nonReentrant {
        uint32 currentTotalBlocksProven = totalBlocksProven;
        for (uint256 i = 0; i < _committedBlocks.length; ++i) {
            //保证和先前提交的块一致
            require(hashStoredBlockInfo(_committedBlocks[i]) == storedBlockHashes[currentTotalBlocksProven + 1], "o1");  
            ++currentTotalBlocksProven;
            // 保证提交的块和proof的承诺一致
            require(_proof.commitments[i] & INPUT_MASK == uint256(_committedBlocks[i].commitment) & INPUT_MASK, "o"); // incorrect block commitment in proof
        }
        
        //采用聚合证明进行验证
        bool success =
            verifier.verifyAggregatedBlockProof(
                _proof.recursiveInput,
                _proof.proof,
                _proof.vkIndexes,
                _proof.commitments,
                _proof.subproofsLimbs
            );
        require(success, "p"); // Aggregated proof verification fail

        require(currentTotalBlocksProven <= totalBlocksCommitted, "q");
        totalBlocksProven = currentTotalBlocksProven;
    }

Block execution

只有验证者执行已经提交的块。

    function executeBlocks(ExecuteBlockInfo[] memory _blocksData) external nonReentrant {
        requireActive();
        governance.requireActiveValidator(msg.sender);

        uint64 priorityRequestsExecuted = 0;
        uint32 nBlocks = uint32(_blocksData.length);
        for (uint32 i = 0; i < nBlocks; ++i) {
            executeOneBlock(_blocksData[i], i);
            priorityRequestsExecuted += _blocksData[i].storedBlock.priorityOperations;
            emit BlockVerification(_blocksData[i].storedBlock.blockNumber);
        }

        firstPriorityRequestId += priorityRequestsExecuted;
        totalCommittedPriorityRequests -= priorityRequestsExecuted;
        totalOpenPriorityRequests -= priorityRequestsExecuted;

        totalBlocksExecuted += nBlocks;
        // 保证只有验证过的块才能执行
        require(totalBlocksExecuted <= totalBlocksProven, "n"); // Can't execute blocks more then committed and proven currently.
    }

Revert Blocks

验证者可以撤销未执行的块。

    /// @notice Reverts unverified blocks
    function revertBlocks(StoredBlockInfo[] memory _blocksToRevert) external nonReentrant {
        governance.requireActiveValidator(msg.sender);

        uint32 blocksCommitted = totalBlocksCommitted;
        uint32 blocksToRevert = Utils.minU32(uint32(_blocksToRevert.length), blocksCommitted - totalBlocksExecuted);
        uint64 revertedPriorityRequests = 0;

        for (uint32 i = 0; i < blocksToRevert; ++i) {
            StoredBlockInfo memory storedBlockInfo = _blocksToRevert[i];
            require(storedBlockHashes[blocksCommitted] == hashStoredBlockInfo(storedBlockInfo), "r"); // incorrect stored block info

            delete storedBlockHashes[blocksCommitted];

            --blocksCommitted;
            revertedPriorityRequests += storedBlockInfo.priorityOperations;
        }

        totalBlocksCommitted = blocksCommitted;
        totalCommittedPriorityRequests -= revertedPriorityRequests;
        if (totalBlocksCommitted < totalBlocksProven) {
            totalBlocksProven = totalBlocksCommitted;
        }

        emit BlocksRevert(totalBlocksExecuted, blocksCommitted);
    }

优先级队列

Priority queue保证 depositfull_exit 操作能够及时处理,并被包含在ZKSync的块中。并且对于full_exit交易,保证用户总是能够取款。

NewPriorityRequest为用户发送交易触发的事件,它的结构为:

  • serialId: 优先请示的id

  • opType: 操作类型

  • pubData: 请求的数据

  • expirationBlock: 请求过期的块号, expirationBlock = block.number + 250, 有效期将近一个小时

当块在Exodus mode中过期的时候,Deposit priority requests 资金返回给用户的root-chain 账户, 然后丢弃Deposit priority requests

fee

当用户发送priority request请求的时候,必须支付额外的费用,费用从DepositFull Exit 中的Ether中扣除,费用的计算方式为:fee = FEE_COEFF * (BASE_GAS + gasleft) * gasprice, 其中:

  • FEE_COEFF: 系数

  • BASE_GAS: 基本的gas费用,为21000

  • gasleft: 交易执行的剩余gas

  • gasprice: gas价格

验证者职责

验证者必须订阅 NewPriorityRequest事件,并且保持正确的顺序,将优先的交易打包到下一个块中。验证者需要尽快打包这些交易,否则会进入Exodus 模式。

Exodus 模式

Request Queue处理过慢,可能触发ZKSync合约进入 Exodus模式,当以太坊目前的区块超过expirationBlock的时候。

    /// @notice Checks if Exodus mode must be entered. If true - enters exodus mode and emits ExodusMode event.
    /// @dev Exodus mode must be entered in case of current ethereum block number is higher than the oldest
    /// @dev of existed priority requests expiration block number.
    /// @return bool flag that is true if the Exodus mode must be entered.
    function activateExodusMode() public returns (bool) {
        bool trigger =
            block.number >= priorityRequests[firstPriorityRequestId].expirationBlock &&
                priorityRequests[firstPriorityRequestId].expirationBlock != 0;
        if ($$(EASY_EXODUS) || trigger) {
            if (!exodusMode) {
                exodusMode = true;
                emit ExodusMode();
            }
            return true;
        } else {
            return false;
        }
    }

Exodus模式下,合约冻结所有的块处理,所有的用户必须退出,所有存在的提交的块将被摊销。

所有的用户可以通过调用exit()合约函数提交SNARK证明,显示其在最新状态下拥有资金。若验证通过,用户的所有资金退回它的onchain balance中。

    function performExodus(
        StoredBlockInfo memory _storedBlockInfo,
        address _owner,
        uint32 _accountId,
        uint16 _tokenId,
        uint128 _amount,
        uint256[] memory _proof
    ) external nonReentrant {
        bytes22 packedBalanceKey = packAddressAndTokenId(_owner, _tokenId);
        require(exodusMode, "s"); // must be in exodus mode
        require(!performedExodus[_accountId][_tokenId], "t"); // already exited
        require(storedBlockHashes[totalBlocksExecuted] == hashStoredBlockInfo(_storedBlockInfo), "u"); // incorrect sotred block info

        bool proofCorrect =
            verifier.verifyExitProof(_storedBlockInfo.stateHash, _accountId, _owner, _tokenId, _amount, _proof);
        require(proofCorrect, "x");

        increaseBalanceToWithdraw(packedBalanceKey, _amount);
        performedExodus[_accountId][_tokenId] = true;
    }

移植

迁移到新的合约比较简单,但必须允许用户可以选择性加入,或允许用户退出。

参考

https://github.com/matter-labs/zksync/blob/master/docs/contract.md

https://github.com/matter-labs/zksync/tree/master/contracts/contracts

相关文章

网友评论

      本文标题:ZkSync链上合约

      本文链接:https://www.haomeiwen.com/subject/xtrvlltx.html