ETH Price: $1,881.72 (-0.62%)
Gas: 0.55 Gwei

Transaction Decoder

Block:
15816020 at Oct-24-2022 05:57:11 AM +UTC
Transaction Fee:
0.000698516798639028 ETH $1.31
Gas Used:
62,618 Gas / 11.155207746 Gwei

Emitted Events:

251 Proxy.0xd0943372c08b438a88d4b39d77216901079eda9ca59d45349841c099083b6830( 0xd0943372c08b438a88d4b39d77216901079eda9ca59d45349841c099083b6830, 0000000000000000000000003533bc39e12d49ba4c427fa43bf69c349d075ff4, 000000000000000000000000000000000000000000000000000000000008ac10, 0000000000000000000000000000000000000000000000000000000000000001, 00000000000000000000000000000000000000000000000000000000000000a0, 0000000000000000000000000000000000000000000000000000000000f29054, 000000000000000000000000000000000000000000000000000000000000002d, 01000000000000000000000000000000000f43fc2c04ee00003533bc39e12d49, ba4c427fa43bf69c349d075ff400000000000000000000000000000000000000 )
252 Proxy.0x8f5f51448394699ad6a3b80cdadf4ec68c5d724c8c3fea09bea55b3c2d0e2dd0( 0x8f5f51448394699ad6a3b80cdadf4ec68c5d724c8c3fea09bea55b3c2d0e2dd0, 0x0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000f43fc2c04ee0000 )

Account State Difference:

  Address   Before After State Difference Code
0x3533bc39...49d075ff4
1.208673606632875 Eth
Nonce: 2
0.107975089834235972 Eth
Nonce: 3
1.100698516798639028
0xaBEA9132...00777fBEF
(zkSync)
22,001.684727181548442853 Eth22,002.784727181548442853 Eth1.1
(bloXroute: Ethical Builder)
2.241150628334616263 Eth2.241187460600015515 Eth0.000036832265399252

Execution Trace

ETH 1.1 Proxy.2d2da806( )
  • ETH 1.1 ZkSync.depositETH( _zkSyncAddress=0x3533bc39E12d49Ba4c427fa43bF69C349d075ff4 )
    File 1 of 2: Proxy
    pragma solidity ^0.5.0;
    import "./Ownable.sol";
    import "./Upgradeable.sol";
    import "./UpgradeableMaster.sol";
    /// @title Proxy Contract
    /// @dev NOTICE: Proxy must implement UpgradeableMaster interface to prevent calling some function of it not by master of proxy
    /// @author Matter Labs
    contract Proxy is Upgradeable, UpgradeableMaster, Ownable {
        /// @notice Storage position of "target" (actual implementation address: keccak256('eip1967.proxy.implementation') - 1)
        bytes32 private constant targetPosition = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
        /// @notice Contract constructor
        /// @dev Calls Ownable contract constructor and initialize target
        /// @param target Initial implementation address
        /// @param targetInitializationParameters Target initialization parameters
        constructor(address target, bytes memory targetInitializationParameters) Ownable(msg.sender) public {
            setTarget(target);
            (bool initializationSuccess, ) = getTarget().delegatecall(
                abi.encodeWithSignature("initialize(bytes)", targetInitializationParameters)
            );
            require(initializationSuccess, "uin11"); // uin11 - target initialization failed
        }
        /// @notice Intercepts initialization calls
        function initialize(bytes calldata) external pure {
            revert("ini11"); // ini11 - interception of initialization call
        }
        /// @notice Intercepts upgrade calls
        function upgrade(bytes calldata) external pure {
            revert("upg11"); // upg11 - interception of upgrade call
        }
        /// @notice Returns target of contract
        /// @return Actual implementation address
        function getTarget() public view returns (address target) {
            bytes32 position = targetPosition;
            assembly {
                target := sload(position)
            }
        }
        /// @notice Sets new target of contract
        /// @param _newTarget New actual implementation address
        function setTarget(address _newTarget) internal {
            bytes32 position = targetPosition;
            assembly {
                sstore(position, _newTarget)
            }
        }
        /// @notice Upgrades target
        /// @param newTarget New target
        /// @param newTargetUpgradeParameters New target upgrade parameters
        function upgradeTarget(address newTarget, bytes calldata newTargetUpgradeParameters) external {
            requireMaster(msg.sender);
            setTarget(newTarget);
            (bool upgradeSuccess, ) = getTarget().delegatecall(
                abi.encodeWithSignature("upgrade(bytes)", newTargetUpgradeParameters)
            );
            require(upgradeSuccess, "ufu11"); // ufu11 - target upgrade failed
        }
        /// @notice Performs a delegatecall to the contract implementation
        /// @dev Fallback function allowing to perform a delegatecall to the given implementation
        /// This function will return whatever the implementation call returns
        function() external payable {
            address _target = getTarget();
            assembly {
                // The pointer to the free memory slot
                let ptr := mload(0x40)
                // Copy function signature and arguments from calldata at zero position into memory at pointer position
                calldatacopy(ptr, 0x0, calldatasize)
                // Delegatecall method of the implementation contract, returns 0 on error
                let result := delegatecall(
                    gas,
                    _target,
                    ptr,
                    calldatasize,
                    0x0,
                    0
                )
                // Get the size of the last return data
                let size := returndatasize
                // Copy the size length of bytes from return data at zero position to pointer position
                returndatacopy(ptr, 0x0, size)
                // Depending on result value
                switch result
                case 0 {
                    // End execution and revert state changes
                    revert(ptr, size)
                }
                default {
                    // Return data with length of size at pointers position
                    return(ptr, size)
                }
            }
        }
        /// UpgradeableMaster functions
        /// @notice Notice period before activation preparation status of upgrade mode
        function getNoticePeriod() external returns (uint) {
            (bool success, bytes memory result) = getTarget().delegatecall(abi.encodeWithSignature("getNoticePeriod()"));
            require(success, "unp11"); // unp11 - upgradeNoticePeriod delegatecall failed
            return abi.decode(result, (uint));
        }
        /// @notice Notifies proxy contract that notice period started
        function upgradeNoticePeriodStarted() external {
            requireMaster(msg.sender);
            (bool success, ) = getTarget().delegatecall(abi.encodeWithSignature("upgradeNoticePeriodStarted()"));
            require(success, "nps11"); // nps11 - upgradeNoticePeriodStarted delegatecall failed
        }
        /// @notice Notifies proxy contract that upgrade preparation status is activated
        function upgradePreparationStarted() external {
            requireMaster(msg.sender);
            (bool success, ) = getTarget().delegatecall(abi.encodeWithSignature("upgradePreparationStarted()"));
            require(success, "ups11"); // ups11 - upgradePreparationStarted delegatecall failed
        }
        /// @notice Notifies proxy contract that upgrade canceled
        function upgradeCanceled() external {
            requireMaster(msg.sender);
            (bool success, ) = getTarget().delegatecall(abi.encodeWithSignature("upgradeCanceled()"));
            require(success, "puc11"); // puc11 - upgradeCanceled delegatecall failed
        }
        /// @notice Notifies proxy contract that upgrade finishes
        function upgradeFinishes() external {
            requireMaster(msg.sender);
            (bool success, ) = getTarget().delegatecall(abi.encodeWithSignature("upgradeFinishes()"));
            require(success, "puf11"); // puf11 - upgradeFinishes delegatecall failed
        }
        /// @notice Checks that contract is ready for upgrade
        /// @return bool flag indicating that contract is ready for upgrade
        function isReadyForUpgrade() external returns (bool) {
            (bool success, bytes memory result) = getTarget().delegatecall(abi.encodeWithSignature("isReadyForUpgrade()"));
            require(success, "rfu11"); // rfu11 - readyForUpgrade delegatecall failed
            return abi.decode(result, (bool));
        }
    }
    pragma solidity ^0.5.0;
    /// @title Ownable Contract
    /// @author Matter Labs
    contract Ownable {
        /// @notice Storage position of the masters address (keccak256('eip1967.proxy.admin') - 1)
        bytes32 private constant masterPosition = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
        /// @notice Contract constructor
        /// @dev Sets msg sender address as masters address
        /// @param masterAddress Master address
        constructor(address masterAddress) public {
            setMaster(masterAddress);
        }
        /// @notice Check if specified address is master
        /// @param _address Address to check
        function requireMaster(address _address) internal view {
            require(_address == getMaster(), "oro11"); // oro11 - only by master
        }
        /// @notice Returns contract masters address
        /// @return Masters address
        function getMaster() public view returns (address master) {
            bytes32 position = masterPosition;
            assembly {
                master := sload(position)
            }
        }
        /// @notice Sets new masters address
        /// @param _newMaster New masters address
        function setMaster(address _newMaster) internal {
            bytes32 position = masterPosition;
            assembly {
                sstore(position, _newMaster)
            }
        }
        /// @notice Transfer mastership of the contract to new master
        /// @param _newMaster New masters address
        function transferMastership(address _newMaster) external {
            requireMaster(msg.sender);
            require(_newMaster != address(0), "otp11"); // otp11 - new masters address can't be zero address
            setMaster(_newMaster);
        }
    }
    pragma solidity ^0.5.0;
    /// @title Interface of the upgradeable contract
    /// @author Matter Labs
    interface Upgradeable {
        /// @notice Upgrades target of upgradeable contract
        /// @param newTarget New target
        /// @param newTargetInitializationParameters New target initialization parameters
        function upgradeTarget(address newTarget, bytes calldata newTargetInitializationParameters) external;
    }
    pragma solidity ^0.5.0;
    /// @title Interface of the upgradeable master contract (defines notice period duration and allows finish upgrade during preparation of it)
    /// @author Matter Labs
    interface UpgradeableMaster {
        /// @notice Notice period before activation preparation status of upgrade mode
        function getNoticePeriod() external returns (uint);
        /// @notice Notifies contract that notice period started
        function upgradeNoticePeriodStarted() external;
        /// @notice Notifies contract that upgrade preparation status is activated
        function upgradePreparationStarted() external;
        /// @notice Notifies contract that upgrade canceled
        function upgradeCanceled() external;
        /// @notice Notifies contract that upgrade finishes
        function upgradeFinishes() external;
        /// @notice Checks that contract is ready for upgrade
        /// @return bool flag indicating that contract is ready for upgrade
        function isReadyForUpgrade() external returns (bool);
    }
    

    File 2 of 2: ZkSync
    pragma solidity ^0.7.0;
    pragma experimental ABIEncoderV2;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    import "./ReentrancyGuard.sol";
    import "./SafeMath.sol";
    import "./SafeMathUInt128.sol";
    import "./SafeCast.sol";
    import "./Utils.sol";
    import "./Storage.sol";
    import "./Config.sol";
    import "./Events.sol";
    import "./Bytes.sol";
    import "./Operations.sol";
    import "./UpgradeableMaster.sol";
    import "./AdditionalZkSync.sol";
    /// @title zkSync main contract
    /// @author Matter Labs
    contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard {
        using SafeMath for uint256;
        using SafeMathUInt128 for uint128;
        bytes32 private constant EMPTY_STRING_KECCAK = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        /// @notice Data needed to process onchain operation from block public data.
        /// @notice Onchain operations is operations that need some processing on L1: Deposits, Withdrawals, ChangePubKey.
        /// @param ethWitness Some external data that can be needed for operation processing
        /// @param publicDataOffset Byte offset in public data for onchain operation
        struct OnchainOperationData {
            bytes ethWitness;
            uint32 publicDataOffset;
        }
        /// @notice Data needed to commit new block
        struct CommitBlockInfo {
            bytes32 newStateHash;
            bytes publicData;
            uint256 timestamp;
            OnchainOperationData[] onchainOperations;
            uint32 blockNumber;
            uint32 feeAccount;
        }
        /// @notice Data needed to execute committed and verified block
        /// @param commitmentsInSlot verified commitments in one slot
        /// @param commitmentIdx index such that commitmentsInSlot[commitmentIdx] is current block commitment
        struct ExecuteBlockInfo {
            StoredBlockInfo storedBlock;
            bytes[] pendingOnchainOpsPubdata;
        }
        /// @notice Recursive proof input data (individual commitments are constructed onchain)
        struct ProofInput {
            uint256[] recursiveInput;
            uint256[] proof;
            uint256[] commitments;
            uint8[] vkIndexes;
            uint256[16] subproofsLimbs;
        }
        // Upgrade functional
        /// @notice Notice period before activation preparation status of upgrade mode
        function getNoticePeriod() external pure override returns (uint256) {
            return 0;
        }
        /// @notice Notification that upgrade notice period started
        /// @dev Can be external because Proxy contract intercepts illegal calls of this function
        function upgradeNoticePeriodStarted() external override {
            upgradeStartTimestamp = block.timestamp;
        }
        /// @notice Notification that upgrade preparation status is activated
        /// @dev Can be external because Proxy contract intercepts illegal calls of this function
        function upgradePreparationStarted() external override {
            require(block.timestamp >= upgradeStartTimestamp.add(approvedUpgradeNoticePeriod));
            upgradePreparationActive = true;
            upgradePreparationActivationTime = block.timestamp;
        }
        /// @dev When upgrade is finished or canceled we must clean upgrade-related state.
        function clearUpgradeStatus() internal {
            upgradePreparationActive = false;
            upgradePreparationActivationTime = 0;
            approvedUpgradeNoticePeriod = UPGRADE_NOTICE_PERIOD;
            emit NoticePeriodChange(approvedUpgradeNoticePeriod);
            upgradeStartTimestamp = 0;
            for (uint256 i = 0; i < SECURITY_COUNCIL_MEMBERS_NUMBER; ++i) {
                securityCouncilApproves[i] = false;
            }
            numberOfApprovalsFromSecurityCouncil = 0;
        }
        /// @notice Notification that upgrade canceled
        /// @dev Can be external because Proxy contract intercepts illegal calls of this function
        function upgradeCanceled() external override {
            clearUpgradeStatus();
        }
        /// @notice Notification that upgrade finishes
        /// @dev Can be external because Proxy contract intercepts illegal calls of this function
        function upgradeFinishes() external override {
            clearUpgradeStatus();
        }
        /// @notice Checks that contract is ready for upgrade
        /// @return bool flag indicating that contract is ready for upgrade
        function isReadyForUpgrade() external pure override returns (bool) {
            return true;
        }
        constructor() {
            initializeReentrancyGuard();
        }
        /// @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,
                address _additionalZkSync,
                bytes32 _genesisStateHash
            ) = abi.decode(initializationParameters, (address, address, address, bytes32));
            verifier = Verifier(_verifierAddress);
            governance = Governance(_governanceAddress);
            additionalZkSync = AdditionalZkSync(_additionalZkSync);
            StoredBlockInfo memory storedBlockZero = StoredBlockInfo(
                0,
                0,
                EMPTY_STRING_KECCAK,
                0,
                _genesisStateHash,
                bytes32(0)
            );
            storedBlockHashes[0] = hashStoredBlockInfo(storedBlockZero);
            approvedUpgradeNoticePeriod = UPGRADE_NOTICE_PERIOD;
            emit NoticePeriodChange(approvedUpgradeNoticePeriod);
        }
        /// @notice zkSync contract upgrade. Can be external because Proxy contract intercepts illegal calls of this function.
        /// @param upgradeParameters Encoded representation of upgrade parameters
        // solhint-disable-next-line no-empty-blocks
        function upgrade(bytes calldata upgradeParameters) external nonReentrant {
            // Silencing the warning "Unused function parameter"
            upgradeParameters;
            approvedUpgradeNoticePeriod = UPGRADE_NOTICE_PERIOD;
            additionalZkSync = AdditionalZkSync(0x2eaa1377e0fC95dE998B9fA7611E9D67ebA534fD);
        }
        function cutUpgradeNoticePeriod(bytes32 targetsHash) external {
            // All functions delegated to additional contract should NOT be nonReentrant
            // Silencing the warning "Unused function parameter"
            targetsHash;
            delegateAdditional();
        }
        function cutUpgradeNoticePeriodBySignature(bytes[] calldata signatures) external {
            // All functions delegated to additional contract should NOT be nonReentrant
            // Silencing the warning "Unused function parameter"
            signatures;
            delegateAdditional();
        }
        /// @notice Sends tokens
        /// @dev NOTE: will revert if transfer call fails or rollup balance difference (before and after transfer) is bigger than _maxAmount
        /// @dev This function is used to allow tokens to spend zkSync contract balance up to amount that is requested
        /// @param _token Token address
        /// @param _to Address of recipient
        /// @param _amount Amount of tokens to transfer
        /// @param _maxAmount Maximum possible amount of tokens to transfer to this account
        function transferERC20(
            IERC20 _token,
            address _to,
            uint128 _amount,
            uint128 _maxAmount
        ) external returns (uint128 withdrawnAmount) {
            require(msg.sender == address(this), "5"); // can be called only from this contract as one "external" call (to revert all this function state changes if it is needed)
            uint256 balanceBefore = _token.balanceOf(address(this));
            _token.transfer(_to, _amount);
            uint256 balanceAfter = _token.balanceOf(address(this));
            uint256 balanceDiff = balanceBefore.sub(balanceAfter);
            require(balanceDiff > 0, "c1"); // transfer is considered successful only if the balance of the contract decreased after transfer
            require(balanceDiff <= _maxAmount, "7"); // rollup balance difference (before and after transfer) is bigger than `_maxAmount`
            // It is safe to convert `balanceDiff` to `uint128` without additional checks, because `balanceDiff <= _maxAmount`
            return uint128(balanceDiff);
        }
        /// @notice Accrues users balances from deposit priority requests in Exodus mode
        /// @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, bytes[] calldata _depositsPubdata) external {
            // All functions delegated to additional contract should NOT be nonReentrant
            // Silencing the warning "Unused function parameter"
            _n;
            _depositsPubdata;
            delegateAdditional();
        }
        /// @notice Deposit ETH to Layer 2 - transfer ether from user into contract, validate it, register deposit
        /// @param _zkSyncAddress The receiver Layer 2 address
        function depositETH(address _zkSyncAddress) external payable {
            require(_zkSyncAddress != SPECIAL_ACCOUNT_ADDRESS, "P");
            require(msg.value > 0, "M"); // Zero-value deposits are forbidden by zkSync rollup logic
            requireActive();
            registerDeposit(0, SafeCast.toUint128(msg.value), _zkSyncAddress);
        }
        /// @notice Deposit ERC20 token to Layer 2 - transfer ERC20 tokens from user into contract, validate it, register deposit
        /// @param _token Token address
        /// @param _amount Token amount
        /// @param _zkSyncAddress Receiver Layer 2 address
        function depositERC20(
            IERC20 _token,
            uint104 _amount,
            address _zkSyncAddress
        ) external nonReentrant {
            require(_zkSyncAddress != SPECIAL_ACCOUNT_ADDRESS, "P");
            requireActive();
            // Get token id by its address
            uint16 tokenId = governance.validateTokenAddress(address(_token));
            require(!governance.pausedTokens(tokenId), "b"); // token deposits are paused
            uint256 balanceBefore = _token.balanceOf(address(this));
            _token.transferFrom(msg.sender, address(this), _amount);
            uint256 balanceAfter = _token.balanceOf(address(this));
            uint128 depositAmount = SafeCast.toUint128(balanceAfter.sub(balanceBefore));
            require(depositAmount > 0 && depositAmount <= MAX_DEPOSIT_AMOUNT, "C");
            registerDeposit(tokenId, depositAmount, _zkSyncAddress);
        }
        /// @notice Returns amount of tokens that can be withdrawn by `address` from zkSync contract
        /// @param _address Address of the tokens owner
        /// @param _token Address of token, zero address is used for ETH
        function getPendingBalance(address _address, address _token) public view returns (uint128) {
            uint16 tokenId = 0;
            if (_token != address(0)) {
                tokenId = governance.validateTokenAddress(_token);
            }
            return pendingBalances[packAddressAndTokenId(_address, tokenId)].balanceToWithdraw;
        }
        /// @notice  Withdraws tokens from zkSync contract to the owner
        /// @param _owner Address of the tokens owner
        /// @param _token Address of tokens, zero address is used for ETH
        /// @param _amount Amount to withdraw to request.
        ///         NOTE: We will call ERC20.transfer(.., _amount), but if according to internal logic of ERC20 token zkSync contract
        ///         balance will be decreased by value more then _amount we will try to subtract this value from user pending balance
        function withdrawPendingBalance(
            address payable _owner,
            address _token,
            uint128 _amount
        ) external nonReentrant {
            uint16 tokenId = 0;
            if (_token != address(0)) {
                tokenId = governance.validateTokenAddress(_token);
            }
            bytes22 packedBalanceKey = packAddressAndTokenId(_owner, tokenId);
            uint128 balance = pendingBalances[packedBalanceKey].balanceToWithdraw;
            uint128 amount = Utils.minU128(balance, _amount);
            require(amount > 0, "f1"); // Nothing to withdraw
            if (tokenId == 0) {
                (bool success, ) = _owner.call{value: amount}("");
                require(success, "d"); // ETH withdraw failed
            } else {
                // We will allow withdrawals of `value` such that:
                // `value` <= user pending balance
                // `value` can be bigger then `amount` requested if token takes fee from sender in addition to `amount` requested
                amount = this.transferERC20(IERC20(_token), _owner, amount, balance);
            }
            pendingBalances[packedBalanceKey].balanceToWithdraw = balance - amount;
            emit Withdrawal(_owner, tokenId, amount);
        }
        /// @notice  Withdraws NFT from zkSync contract to the owner
        /// @param _tokenId Id of NFT token
        function withdrawPendingNFTBalance(uint32 _tokenId) external nonReentrant {
            Operations.WithdrawNFT memory op = pendingWithdrawnNFTs[_tokenId];
            require(op.creatorAddress != address(0), "op"); // No NFT to withdraw
            NFTFactory _factory = governance.getNFTFactory(op.creatorAccountId, op.creatorAddress);
            // Save withdrawn nfts for future deposits
            withdrawnNFTs[_tokenId] = address(_factory);
            delete pendingWithdrawnNFTs[_tokenId];
            _factory.mintNFTFromZkSync(
                op.creatorAddress,
                op.receiver,
                op.creatorAccountId,
                op.serialId,
                op.contentHash,
                op.tokenId
            );
            emit WithdrawalNFT(op.tokenId);
        }
        /// @notice Register full exit request - pack pubdata, add priority request
        /// @param _accountId Numerical id of the account
        /// @param _token Token address, 0 address for ether
        function requestFullExit(uint32 _accountId, address _token) public nonReentrant {
            requireActive();
            require(_accountId <= MAX_ACCOUNT_ID, "e");
            require(_accountId != SPECIAL_ACCOUNT_ID, "v"); // request full exit for nft storage account
            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
                nftCreatorAccountId: uint32(0), // unknown at this point
                nftCreatorAddress: address(0), // unknown at this point
                nftSerialId: uint32(0), // unknown at this point
                nftContentHash: bytes32(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;
        }
        /// @notice Register full exit nft request - pack pubdata, add priority request
        /// @param _accountId Numerical id of the account
        /// @param _tokenId NFT token id in zkSync network
        function requestFullExitNFT(uint32 _accountId, uint32 _tokenId) public nonReentrant {
            requireActive();
            require(_accountId <= MAX_ACCOUNT_ID, "e");
            require(_accountId != SPECIAL_ACCOUNT_ID, "v"); // request full exit nft for nft storage account
            require(MAX_FUNGIBLE_TOKEN_ID < _tokenId && _tokenId < SPECIAL_NFT_TOKEN_ID, "T"); // request full exit nft for invalid token id
            // Priority Queue request
            Operations.FullExit memory op = Operations.FullExit({
                accountId: _accountId,
                owner: msg.sender,
                tokenId: _tokenId,
                amount: 0, // unknown at this point
                nftCreatorAccountId: uint32(0), // unknown at this point
                nftCreatorAddress: address(0), // unknown at this point
                nftSerialId: uint32(0), // unknown at this point
                nftContentHash: bytes32(0) // unknown at this point
            });
            bytes memory pubData = Operations.writeFullExitPubdataForPriorityQueue(op);
            addPriorityRequest(Operations.OpType.FullExit, pubData);
        }
        /// @dev Process one block commit using previous block StoredBlockInfo,
        /// @dev returns new block StoredBlockInfo
        function commitOneBlock(StoredBlockInfo memory _previousBlock, CommitBlockInfo calldata _newBlock)
            internal
            view
            returns (StoredBlockInfo memory storedNewBlock)
        {
            require(_newBlock.blockNumber == _previousBlock.blockNumber + 1, "f"); // only commit next block
            // Check timestamp of the new block
            {
                require(_newBlock.timestamp >= _previousBlock.timestamp, "g"); // Block should be after previous block
                bool timestampNotTooSmall = block.timestamp.sub(COMMIT_TIMESTAMP_NOT_OLDER) <= _newBlock.timestamp;
                bool timestampNotTooBig = _newBlock.timestamp <= block.timestamp.add(COMMIT_TIMESTAMP_APPROXIMATION_DELTA);
                require(timestampNotTooSmall && timestampNotTooBig, "h"); // New block timestamp is not valid
            }
            // Check onchain operations
            (
                bytes32 pendingOnchainOpsHash,
                uint64 priorityReqCommitted,
                bytes memory onchainOpsOffsetCommitment
            ) = collectOnchainOps(_newBlock);
            // Create block commitment for verification proof
            bytes32 commitment = createBlockCommitment(_previousBlock, _newBlock, onchainOpsOffsetCommitment);
            return
                StoredBlockInfo(
                    _newBlock.blockNumber,
                    priorityReqCommitted,
                    pendingOnchainOpsHash,
                    _newBlock.timestamp,
                    _newBlock.newStateHash,
                    commitment
                );
        }
        /// @notice Commit block
        /// @notice 1. Checks onchain operations, timestamp.
        /// @notice 2. Store block commitments
        function commitBlocks(StoredBlockInfo memory _lastCommittedBlockData, CommitBlockInfo[] calldata _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
            uint32 newBlocksDataLength = uint32(_newBlocksData.length);
            for (uint256 i = 0; i < newBlocksDataLength; ++i) {
                _lastCommittedBlockData = commitOneBlock(_lastCommittedBlockData, _newBlocksData[i]);
                totalCommittedPriorityRequests += _lastCommittedBlockData.priorityOperations;
                storedBlockHashes[_lastCommittedBlockData.blockNumber] = hashStoredBlockInfo(_lastCommittedBlockData);
                emit BlockCommit(_lastCommittedBlockData.blockNumber);
            }
            totalBlocksCommitted += newBlocksDataLength;
            require(totalCommittedPriorityRequests <= totalOpenPriorityRequests, "j");
        }
        /// @dev Save NFT as pending to withdraw
        function storePendingNFT(Operations.WithdrawNFT memory op) internal {
            pendingWithdrawnNFTs[op.tokenId] = op;
            emit WithdrawalNFTPending(op.tokenId);
        }
        /// @dev Increase `_recipient` balance to withdraw
        function increasePendingBalance(
            uint16 _tokenId,
            address _recipient,
            uint128 _amount,
            Operations.WithdrawalType _withdrawalType
        ) internal {
            bytes22 packedBalanceKey = packAddressAndTokenId(_recipient, _tokenId);
            increaseBalanceToWithdraw(packedBalanceKey, _amount);
            emit WithdrawalPending(_tokenId, _recipient, _amount, _withdrawalType);
        }
        /// @dev Executes one block
        /// @dev 1. Processes all priority operations or save them as pending
        /// @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 calldata _blockExecuteData, uint256 _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;
            uint256 pendingOnchainOpsPubdataLength = _blockExecuteData.pendingOnchainOpsPubdata.length;
            for (uint256 i = 0; i < pendingOnchainOpsPubdataLength; ++i) {
                bytes calldata pubData = _blockExecuteData.pendingOnchainOpsPubdata[i];
                Operations.OpType opType = Operations.OpType(uint8(pubData[0]));
                if (opType == Operations.OpType.PartialExit) {
                    Operations.PartialExit memory op = Operations.readPartialExitPubdata(pubData);
                    // Circuit guarantees that partial exits are available only for fungible tokens
                    require(op.tokenId <= MAX_FUNGIBLE_TOKEN_ID, "mf1");
                    increasePendingBalance(uint16(op.tokenId), op.owner, op.amount, Operations.WithdrawalType.PartialExit);
                } else if (opType == Operations.OpType.ForcedExit) {
                    Operations.ForcedExit memory op = Operations.readForcedExitPubdata(pubData);
                    // Circuit guarantees that forced exits are available only for fungible tokens
                    require(op.tokenId <= MAX_FUNGIBLE_TOKEN_ID, "mf2");
                    increasePendingBalance(uint16(op.tokenId), op.target, op.amount, Operations.WithdrawalType.ForcedExit);
                } else if (opType == Operations.OpType.FullExit) {
                    Operations.FullExit memory op = Operations.readFullExitPubdata(pubData);
                    if (op.tokenId <= MAX_FUNGIBLE_TOKEN_ID) {
                        increasePendingBalance(uint16(op.tokenId), op.owner, op.amount, Operations.WithdrawalType.FullExit);
                    } else {
                        if (op.amount == 1) {
                            Operations.WithdrawNFT memory withdrawNftOp = Operations.WithdrawNFT(
                                op.nftCreatorAccountId,
                                op.nftCreatorAddress,
                                op.nftSerialId,
                                op.nftContentHash,
                                op.owner,
                                op.tokenId
                            );
                            storePendingNFT(withdrawNftOp);
                        }
                    }
                } else if (opType == Operations.OpType.WithdrawNFT) {
                    Operations.WithdrawNFT memory op = Operations.readWithdrawNFTPubdata(pubData);
                    storePendingNFT(op);
                } else {
                    revert("l"); // unsupported op in block execution
                }
                pendingOnchainOpsHash = Utils.concatHash(pendingOnchainOpsHash, pubData);
            }
            require(pendingOnchainOpsHash == _blockExecuteData.storedBlock.pendingOnchainOperationsHash, "m"); // incorrect onchain ops executed
        }
        /// @notice Execute blocks, completing priority operations and processing withdrawals.
        /// @notice 1. Processes all pending operations (Send Exits, Complete priority requests)
        /// @notice 2. Finalizes block on Ethereum
        function executeBlocks(ExecuteBlockInfo[] calldata _blocksData) external nonReentrant {
            requireActive();
            governance.requireActiveValidator(msg.sender);
            uint64 priorityRequestsExecuted = 0;
            uint32 nBlocks = uint32(_blocksData.length);
            for (uint256 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.
        }
        /// @notice Blocks commitment verification.
        /// @notice Only verifies block commitments without any other processing
        function proveBlocks(StoredBlockInfo[] calldata _committedBlocks, ProofInput memory _proof) external nonReentrant {
            requireActive();
            uint256 i;
            uint32 currentTotalBlocksProven = totalBlocksProven;
            // Preventing "stack too deep error"
            {
                // Ignoring the _committedBlocks that are already proven.
                bytes32 firstUnverifiedBlockHash = storedBlockHashes[currentTotalBlocksProven + 1];
                while (hashStoredBlockInfo(_committedBlocks[i]) != firstUnverifiedBlockHash) {
                    ++i;
                    require(i < _committedBlocks.length, "o2"); // Revert if the first unverified block is not among committed ones.
                }
            }
            uint256 committedBlocksLength = _committedBlocks.length;
            while (i < committedBlocksLength) {
                require(hashStoredBlockInfo(_committedBlocks[i]) == storedBlockHashes[currentTotalBlocksProven + 1], "o1");
                ++currentTotalBlocksProven;
                require(_proof.commitments[i] & INPUT_MASK == uint256(_committedBlocks[i].commitment) & INPUT_MASK, "o"); // incorrect block commitment in proof
                ++i;
            }
            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;
        }
        /// @notice Reverts unverified blocks
        function revertBlocks(StoredBlockInfo[] calldata _blocksToRevert) external {
            // All functions delegated to additional contract should NOT be nonReentrant
            // Silencing the warning "Unused function parameter"
            _blocksToRevert;
            delegateAdditional();
        }
        /// @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() external nonReentrant returns (bool) {
            if (exodusMode) {
                return false;
            }
            bool trigger = block.number >= priorityRequests[firstPriorityRequestId].expirationBlock &&
                priorityRequests[firstPriorityRequestId].expirationBlock != 0;
            if (trigger) {
                exodusMode = true;
                emit ExodusMode();
            }
            return trigger;
        }
        /// @notice Withdraws token from ZkSync to root chain in case of exodus mode. User must provide proof that he owns funds
        /// @param _storedBlockInfo Last verified block
        /// @param _owner Owner of the account
        /// @param _accountId Id of the account in the tree
        /// @param _proof Proof
        /// @param _tokenId Verified token id
        /// @param _amount Amount for owner (must be total amount, not part of it)
        function performExodus(
            StoredBlockInfo calldata _storedBlockInfo,
            address _owner,
            uint32 _accountId,
            uint32 _tokenId,
            uint128 _amount,
            uint32 _nftCreatorAccountId,
            address _nftCreatorAddress,
            uint32 _nftSerialId,
            bytes32 _nftContentHash,
            uint256[] calldata _proof
        ) external {
            // All functions delegated to additional should NOT be nonReentrant
            // Silencing the warning "Unused function parameter"
            _storedBlockInfo;
            _owner;
            _accountId;
            _tokenId;
            _amount;
            _nftCreatorAccountId;
            _nftCreatorAddress;
            _nftSerialId;
            _nftContentHash;
            _proof;
            delegateAdditional();
        }
        /// @notice Set data for changing pubkey hash using onchain authorization.
        ///         Transaction author (msg.sender) should be L2 account address
        /// @notice New pubkey hash can be reset, to do that user should send two transactions:
        ///         1) First `setAuthPubkeyHash` transaction for already used `_nonce` will set timer.
        ///         2) After `AUTH_FACT_RESET_TIMELOCK` time is passed second `setAuthPubkeyHash` transaction will reset pubkey hash for `_nonce`.
        /// @param _pubkeyHash New pubkey hash
        /// @param _nonce Nonce of the change pubkey L2 transaction
        function setAuthPubkeyHash(bytes calldata _pubkeyHash, uint32 _nonce) external {
            // All functions delegated to additional contract should NOT be nonReentrant
            // Silencing the warning "Unused function parameter"
            _nonce;
            _pubkeyHash;
            delegateAdditional();
        }
        /// @notice Register deposit request - pack pubdata, add priority request and emit OnchainDeposit event
        /// @param _tokenId Token by id
        /// @param _amount Token amount
        /// @param _owner Receiver
        function registerDeposit(
            uint16 _tokenId,
            uint128 _amount,
            address _owner
        ) internal {
            // Priority Queue request
            Operations.Deposit memory op = Operations.Deposit({
                accountId: 0, // unknown at this point
                owner: _owner,
                tokenId: _tokenId,
                amount: _amount
            });
            bytes memory pubData = Operations.writeDepositPubdataForPriorityQueue(op);
            addPriorityRequest(Operations.OpType.Deposit, pubData);
            emit Deposit(_tokenId, _amount);
        }
        /// @dev Gets operations packed in bytes array. Unpacks it and stores onchain operations.
        /// @dev Priority operations must be committed in the same order as they are in the priority queue.
        /// @dev NOTE: does not change storage! (only emits events)
        /// @dev processableOperationsHash - hash of the all operations that needs to be executed  (Deposit, Exits, ChangPubKey)
        /// @dev priorityOperationsProcessed - number of priority operations processed in this block (Deposits, FullExits)
        /// @dev offsetsCommitment - array where 1 is stored in chunk where onchainOperation begins and other are 0 (used in commitments)
        function collectOnchainOps(CommitBlockInfo calldata _newBlockData)
            internal
            view
            returns (
                bytes32 processableOperationsHash,
                uint64 priorityOperationsProcessed,
                bytes memory offsetsCommitment
            )
        {
            bytes memory pubData = _newBlockData.publicData;
            uint64 uncommittedPriorityRequestsOffset = firstPriorityRequestId + totalCommittedPriorityRequests;
            priorityOperationsProcessed = 0;
            processableOperationsHash = EMPTY_STRING_KECCAK;
            require(pubData.length % CHUNK_BYTES == 0, "A"); // pubdata length must be a multiple of CHUNK_BYTES
            offsetsCommitment = new bytes(pubData.length / CHUNK_BYTES);
            uint256 newBlockDataOnchainOperationsLength = _newBlockData.onchainOperations.length;
            for (uint256 i = 0; i < newBlockDataOnchainOperationsLength; ++i) {
                OnchainOperationData calldata onchainOpData = _newBlockData.onchainOperations[i];
                uint256 pubdataOffset = onchainOpData.publicDataOffset;
                require(pubdataOffset < pubData.length, "A1");
                require(pubdataOffset % CHUNK_BYTES == 0, "B"); // offsets should be on chunks boundaries
                uint256 chunkId = pubdataOffset / CHUNK_BYTES;
                require(offsetsCommitment[chunkId] == 0x00, "C"); // offset commitment should be empty
                offsetsCommitment[chunkId] = bytes1(0x01);
                Operations.OpType opType = Operations.OpType(uint8(pubData[pubdataOffset]));
                if (opType == Operations.OpType.Deposit) {
                    bytes memory opPubData = Bytes.slice(pubData, pubdataOffset, DEPOSIT_BYTES);
                    Operations.Deposit memory depositData = Operations.readDepositPubdata(opPubData);
                    checkPriorityOperation(depositData, uncommittedPriorityRequestsOffset + priorityOperationsProcessed);
                    ++priorityOperationsProcessed;
                } else if (opType == Operations.OpType.ChangePubKey) {
                    bytes memory opPubData = Bytes.slice(pubData, pubdataOffset, CHANGE_PUBKEY_BYTES);
                    Operations.ChangePubKey memory op = Operations.readChangePubKeyPubdata(opPubData);
                    if (onchainOpData.ethWitness.length != 0) {
                        bool valid = verifyChangePubkey(onchainOpData.ethWitness, op);
                        require(valid, "D"); // failed to verify change pubkey hash signature
                    } else {
                        bool valid = authFacts[op.owner][op.nonce] == keccak256(abi.encodePacked(op.pubKeyHash));
                        require(valid, "E"); // new pub key hash is not authenticated properly
                    }
                } else {
                    bytes memory opPubData;
                    if (opType == Operations.OpType.PartialExit) {
                        opPubData = Bytes.slice(pubData, pubdataOffset, PARTIAL_EXIT_BYTES);
                    } else if (opType == Operations.OpType.ForcedExit) {
                        opPubData = Bytes.slice(pubData, pubdataOffset, FORCED_EXIT_BYTES);
                    } else if (opType == Operations.OpType.WithdrawNFT) {
                        opPubData = Bytes.slice(pubData, pubdataOffset, WITHDRAW_NFT_BYTES);
                    } else if (opType == Operations.OpType.FullExit) {
                        opPubData = Bytes.slice(pubData, pubdataOffset, FULL_EXIT_BYTES);
                        Operations.FullExit memory fullExitData = Operations.readFullExitPubdata(opPubData);
                        checkPriorityOperation(
                            fullExitData,
                            uncommittedPriorityRequestsOffset + priorityOperationsProcessed
                        );
                        ++priorityOperationsProcessed;
                    } else {
                        revert("F"); // unsupported op
                    }
                    processableOperationsHash = Utils.concatHash(processableOperationsHash, opPubData);
                }
            }
        }
        /// @notice Checks that change operation is correct
        function verifyChangePubkey(bytes calldata _ethWitness, Operations.ChangePubKey memory _changePk)
            internal
            pure
            returns (bool)
        {
            Operations.ChangePubkeyType changePkType = Operations.ChangePubkeyType(uint8(_ethWitness[0]));
            if (changePkType == Operations.ChangePubkeyType.ECRECOVER) {
                return verifyChangePubkeyECRECOVER(_ethWitness, _changePk);
            } else if (changePkType == Operations.ChangePubkeyType.CREATE2) {
                return verifyChangePubkeyCREATE2(_ethWitness, _changePk);
            } else if (changePkType == Operations.ChangePubkeyType.OldECRECOVER) {
                return verifyChangePubkeyOldECRECOVER(_ethWitness, _changePk);
            } else if (changePkType == Operations.ChangePubkeyType.ECRECOVERV2) {
                return verifyChangePubkeyECRECOVERV2(_ethWitness, _changePk);
            } else if (changePkType == Operations.ChangePubkeyType.EIP712) {
                return verifyChangePubkeyEIP712(_ethWitness, _changePk);
            } else {
                revert("G"); // Incorrect ChangePubKey type
            }
        }
        /// @notice Checks that signature is valid for pubkey change message
        /// @param _ethWitness Signature (65 bytes)
        /// @param _changePk Parsed change pubkey operation
        function verifyChangePubkeyECRECOVER(bytes calldata _ethWitness, Operations.ChangePubKey memory _changePk)
            internal
            pure
            returns (bool)
        {
            (, bytes memory signature) = Bytes.read(_ethWitness, 1, 65); // offset is 1 because we skip type of ChangePubkey
            bytes32 messageHash = keccak256(
                abi.encodePacked(
                    "\\x19Ethereum Signed Message:\
    60",
                    _changePk.pubKeyHash,
                    _changePk.nonce,
                    _changePk.accountId,
                    bytes32(0)
                )
            );
            address recoveredAddress = Utils.recoverAddressFromEthSignature(signature, messageHash);
            return recoveredAddress == _changePk.owner;
        }
        /// @notice Checks that signature is valid for pubkey change EIP712 message
        /// @param _ethWitness Signature (65 bytes)
        /// @param _changePk Parsed change pubkey operation
        function verifyChangePubkeyEIP712(bytes calldata _ethWitness, Operations.ChangePubKey memory _changePk)
            internal
            pure
            returns (bool)
        {
            (, bytes memory signature) = Bytes.read(_ethWitness, 1, 65); // offset is 1 because we skip type of ChangePubkey
            bytes32 eip712DomainSeparator = keccak256(
                abi.encode(EIP712_DOMAIN_TYPEHASH, keccak256(bytes(name)), keccak256(bytes(version)), Utils.getChainId())
            );
            bytes32 structHash = keccak256(
                abi.encode(EIP712_CHANGEPUBKEY_TYPEHASH, _changePk.pubKeyHash, _changePk.nonce, _changePk.accountId)
            );
            bytes32 digest = keccak256(abi.encodePacked("\\x19\\x01", eip712DomainSeparator, structHash));
            address recoveredAddress = Utils.recoverAddressFromEthSignature(signature, digest);
            return recoveredAddress == _changePk.owner;
        }
        /// @notice Checks that signature is valid for pubkey change message
        /// @param _ethWitness Signature (65 bytes) + 32 bytes of the arbitrary signed data
        /// @notice additional 32 bytes can be used to sign batches and ChangePubKey with one signature
        /// @param _changePk Parsed change pubkey operation
        function verifyChangePubkeyECRECOVERV2(bytes calldata _ethWitness, Operations.ChangePubKey memory _changePk)
            internal
            pure
            returns (bool)
        {
            (uint256 offset, bytes memory signature) = Bytes.read(_ethWitness, 1, 65); // offset is 1 because we skip type of ChangePubkey
            (, bytes32 additionalData) = Bytes.readBytes32(_ethWitness, offset);
            bytes32 messageHash = keccak256(
                abi.encodePacked(
                    "\\x19Ethereum Signed Message:\
    60",
                    _changePk.pubKeyHash,
                    _changePk.nonce,
                    _changePk.accountId,
                    additionalData
                )
            );
            address recoveredAddress = Utils.recoverAddressFromEthSignature(signature, messageHash);
            return recoveredAddress == _changePk.owner;
        }
        /// @notice Checks that signature is valid for pubkey change message, old version differs by form of the signed message.
        /// @param _ethWitness Signature (65 bytes)
        /// @param _changePk Parsed change pubkey operation
        function verifyChangePubkeyOldECRECOVER(bytes calldata _ethWitness, Operations.ChangePubKey memory _changePk)
            internal
            pure
            returns (bool)
        {
            (, bytes memory signature) = Bytes.read(_ethWitness, 1, 65); // offset is 1 because we skip type of ChangePubkey
            bytes32 messageHash = keccak256(
                abi.encodePacked(
                    "\\x19Ethereum Signed Message:\
    152",
                    "Register zkSync pubkey:\
    \
    ",
                    Bytes.bytesToHexASCIIBytes(abi.encodePacked(_changePk.pubKeyHash)),
                    "\
    ",
                    "nonce: 0x",
                    Bytes.bytesToHexASCIIBytes(Bytes.toBytesFromUInt32(_changePk.nonce)),
                    "\
    ",
                    "account id: 0x",
                    Bytes.bytesToHexASCIIBytes(Bytes.toBytesFromUInt32(_changePk.accountId)),
                    "\
    \
    ",
                    "Only sign this message for a trusted client!"
                )
            );
            address recoveredAddress = Utils.recoverAddressFromEthSignature(signature, messageHash);
            return recoveredAddress == _changePk.owner;
        }
        /// @notice Checks that signature is valid for pubkey change message
        /// @param _ethWitness Create2 deployer address, saltArg, codeHash
        /// @param _changePk Parsed change pubkey operation
        function verifyChangePubkeyCREATE2(bytes calldata _ethWitness, Operations.ChangePubKey memory _changePk)
            internal
            pure
            returns (bool)
        {
            address creatorAddress;
            bytes32 saltArg; // salt arg is additional bytes that are encoded in the CREATE2 salt
            bytes32 codeHash;
            uint256 offset = 1; // offset is 1 because we skip type of ChangePubkey
            (offset, creatorAddress) = Bytes.readAddress(_ethWitness, offset);
            (offset, saltArg) = Bytes.readBytes32(_ethWitness, offset);
            (offset, codeHash) = Bytes.readBytes32(_ethWitness, offset);
            // salt from CREATE2 specification
            bytes32 salt = keccak256(abi.encodePacked(saltArg, _changePk.pubKeyHash));
            // Address computation according to CREATE2 definition: https://eips.ethereum.org/EIPS/eip-1014
            address recoveredAddress = address(
                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), creatorAddress, salt, codeHash))))
            );
            // This type of change pubkey can be done only once
            return recoveredAddress == _changePk.owner && _changePk.nonce == 0;
        }
        /// @dev Creates block commitment from its data
        /// @dev _offsetCommitment - hash of the array where 1 is stored in chunk where onchainOperation begins and 0 for other chunks
        function createBlockCommitment(
            StoredBlockInfo memory _previousBlock,
            CommitBlockInfo calldata _newBlockData,
            bytes memory _offsetCommitment
        ) internal view returns (bytes32 commitment) {
            bytes32 hash = sha256(abi.encodePacked(uint256(_newBlockData.blockNumber), uint256(_newBlockData.feeAccount)));
            hash = sha256(abi.encodePacked(hash, _previousBlock.stateHash));
            hash = sha256(abi.encodePacked(hash, _newBlockData.newStateHash));
            hash = sha256(abi.encodePacked(hash, uint256(_newBlockData.timestamp)));
            bytes memory pubdata = abi.encodePacked(_newBlockData.publicData, _offsetCommitment);
            /// The code below is equivalent to `commitment = sha256(abi.encodePacked(hash, _publicData))`
            /// We use inline assembly instead of this concise and readable code in order to avoid copying of `_publicData` (which saves ~90 gas per transfer operation).
            /// Specifically, we perform the following trick:
            /// First, replace the first 32 bytes of `_publicData` (where normally its length is stored) with the value of `hash`.
            /// Then, we call `sha256` precompile passing the `_publicData` pointer and the length of the concatenated byte buffer.
            /// Finally, we put the `_publicData.length` back to its original location (to the first word of `_publicData`).
            assembly {
                let hashResult := mload(0x40)
                let pubDataLen := mload(pubdata)
                mstore(pubdata, hash)
                // staticcall to the sha256 precompile at address 0x2
                let success := staticcall(gas(), 0x2, pubdata, add(pubDataLen, 0x20), hashResult, 0x20)
                mstore(pubdata, pubDataLen)
                // Use "invalid" to make gas estimation work
                switch success
                case 0 {
                    invalid()
                }
                commitment := mload(hashResult)
            }
        }
        /// @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");
        }
        /// @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, "J"); // incorrect priority op type
            bytes20 hashedPubdata = priorityRequests[_priorityRequestId].hashedPubData;
            require(Operations.checkFullExitInPriorityQueue(_fullExit, hashedPubdata), "K");
        }
        // Priority queue
        /// @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 += 1;
        }
        function increaseBalanceToWithdraw(bytes22 _packedBalanceKey, uint128 _amount) internal {
            uint128 balance = pendingBalances[_packedBalanceKey].balanceToWithdraw;
            pendingBalances[_packedBalanceKey] = PendingBalance(balance.add(_amount), FILLED_GAS_RESERVE_VALUE);
        }
        /// @notice Delegates the call to the additional part of the main contract.
        /// @notice Should be only use to delegate the external calls as it passes the calldata
        /// @notice All functions delegated to additional contract should NOT be nonReentrant
        function delegateAdditional() internal {
            address _target = address(additionalZkSync);
            assembly {
                // The pointer to the free memory slot
                let ptr := mload(0x40)
                // Copy function signature and arguments from calldata at zero position into memory at pointer position
                calldatacopy(ptr, 0x0, calldatasize())
                // Delegatecall method of the implementation contract, returns 0 on error
                let result := delegatecall(gas(), _target, ptr, calldatasize(), 0x0, 0)
                // Get the size of the last return data
                let size := returndatasize()
                // Copy the size length of bytes from return data at zero position to pointer position
                returndatacopy(ptr, 0x0, size)
                // Depending on result value
                switch result
                case 0 {
                    // End execution and revert state changes
                    revert(ptr, size)
                }
                default {
                    // Return data with length of size at pointers position
                    return(ptr, size)
                }
            }
        }
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    /**
     * @dev Contract module that helps prevent reentrant calls to a function.
     *
     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
     * available, which can be applied to functions to make sure there are no nested
     * (reentrant) calls to them.
     *
     * Note that because there is a single `nonReentrant` guard, functions marked as
     * `nonReentrant` may not call one another. This can be worked around by making
     * those functions `private`, and then adding `external` `nonReentrant` entry
     * points to them.
     *
     * TIP: If you would like to learn more about reentrancy and alternative ways
     * to protect against it, check out our blog post
     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
     *
     * _Since v2.5.0:_ this module is now much more gas efficient, given net gas
     * metering changes introduced in the Istanbul hardfork.
     */
    contract ReentrancyGuard {
        /// @dev Address of lock flag variable.
        /// @dev Flag is placed at random memory location to not interfere with Storage contract.
        uint256 private constant LOCK_FLAG_ADDRESS = 0x8e94fed44239eb2314ab7a406345e6c5a8f0ccedf3b600de3d004e672c33abf4; // keccak256("ReentrancyGuard") - 1;
        // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/566a774222707e424896c0c390a84dc3c13bdcb2/contracts/security/ReentrancyGuard.sol
        // The values being non-zero value makes deployment a bit more expensive,
        // but in exchange the refund on every call to nonReentrant will be lower in
        // amount. Since refunds are capped to a percentage of the total
        // transaction's gas, it is best to keep them low in cases like this one, to
        // increase the likelihood of the full refund coming into effect.
        uint256 private constant _NOT_ENTERED = 1;
        uint256 private constant _ENTERED = 2;
        function initializeReentrancyGuard() internal {
            uint256 lockSlotOldValue;
            // Storing an initial non-zero value makes deployment a bit more
            // expensive, but in exchange every call to nonReentrant
            // will be cheaper.
            assembly {
                lockSlotOldValue := sload(LOCK_FLAG_ADDRESS)
                sstore(LOCK_FLAG_ADDRESS, _NOT_ENTERED)
            }
            // Check that storage slot for reentrancy guard is empty to rule out possibility of double initialization
            require(lockSlotOldValue == 0, "1B");
        }
        /**
         * @dev Prevents a contract from calling itself, directly or indirectly.
         * Calling a `nonReentrant` function from another `nonReentrant`
         * function is not supported. It is possible to prevent this from happening
         * by making the `nonReentrant` function external, and make it call a
         * `private` function that does the actual work.
         */
        modifier nonReentrant() {
            uint256 _status;
            assembly {
                _status := sload(LOCK_FLAG_ADDRESS)
            }
            // On the first call to nonReentrant, _notEntered will be true
            require(_status == _NOT_ENTERED);
            // Any calls to nonReentrant after this point will fail
            assembly {
                sstore(LOCK_FLAG_ADDRESS, _ENTERED)
            }
            _;
            // By storing the original value once again, a refund is triggered (see
            // https://eips.ethereum.org/EIPS/eip-2200)
            assembly {
                sstore(LOCK_FLAG_ADDRESS, _NOT_ENTERED)
            }
        }
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    /**
     * @dev Wrappers over Solidity's arithmetic operations with added overflow
     * checks.
     *
     * Arithmetic operations in Solidity wrap on overflow. This can easily result
     * in bugs, because programmers usually assume that an overflow raises an
     * error, which is the standard behavior in high level programming languages.
     * `SafeMath` restores this intuition by reverting the transaction when an
     * operation overflows.
     *
     * Using this library instead of the unchecked operations eliminates an entire
     * class of bugs, so it's recommended to use it always.
     */
    library SafeMath {
        /**
         * @dev Returns the addition of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `+` operator.
         *
         * Requirements:
         * - Addition cannot overflow.
         */
        function add(uint256 a, uint256 b) internal pure returns (uint256) {
            uint256 c = a + b;
            require(c >= a, "14");
            return c;
        }
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
            return sub(a, b, "v");
        }
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         * - Subtraction cannot overflow.
         *
         * _Available since v2.4.0._
         */
        function sub(
            uint256 a,
            uint256 b,
            string memory errorMessage
        ) internal pure returns (uint256) {
            require(b <= a, errorMessage);
            uint256 c = a - b;
            return c;
        }
        /**
         * @dev Returns the multiplication of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `*` operator.
         *
         * Requirements:
         * - Multiplication cannot overflow.
         */
        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) {
                return 0;
            }
            uint256 c = a * b;
            require(c / a == b, "15");
            return c;
        }
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b) internal pure returns (uint256) {
            return div(a, b, "x");
        }
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         *
         * _Available since v2.4.0._
         */
        function div(
            uint256 a,
            uint256 b,
            string memory errorMessage
        ) internal pure returns (uint256) {
            // Solidity only automatically asserts when dividing by 0
            require(b > 0, errorMessage);
            uint256 c = a / b;
            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
            return c;
        }
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
            return mod(a, b, "y");
        }
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts with custom message when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         *
         * _Available since v2.4.0._
         */
        function mod(
            uint256 a,
            uint256 b,
            string memory errorMessage
        ) internal pure returns (uint256) {
            require(b != 0, errorMessage);
            return a % b;
        }
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    /**
     * @dev Wrappers over Solidity's arithmetic operations with added overflow
     * checks.
     *
     * Arithmetic operations in Solidity wrap on overflow. This can easily result
     * in bugs, because programmers usually assume that an overflow raises an
     * error, which is the standard behavior in high level programming languages.
     * `SafeMath` restores this intuition by reverting the transaction when an
     * operation overflows.
     *
     * Using this library instead of the unchecked operations eliminates an entire
     * class of bugs, so it's recommended to use it always.
     */
    library SafeMathUInt128 {
        /**
         * @dev Returns the addition of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `+` operator.
         *
         * Requirements:
         * - Addition cannot overflow.
         */
        function add(uint128 a, uint128 b) internal pure returns (uint128) {
            uint128 c = a + b;
            require(c >= a, "12");
            return c;
        }
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         * - Subtraction cannot overflow.
         */
        function sub(uint128 a, uint128 b) internal pure returns (uint128) {
            return sub(a, b, "aa");
        }
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         * - Subtraction cannot overflow.
         *
         * _Available since v2.4.0._
         */
        function sub(
            uint128 a,
            uint128 b,
            string memory errorMessage
        ) internal pure returns (uint128) {
            require(b <= a, errorMessage);
            uint128 c = a - b;
            return c;
        }
        /**
         * @dev Returns the multiplication of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `*` operator.
         *
         * Requirements:
         * - Multiplication cannot overflow.
         */
        function mul(uint128 a, uint128 b) internal pure returns (uint128) {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) {
                return 0;
            }
            uint128 c = a * b;
            require(c / a == b, "13");
            return c;
        }
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function div(uint128 a, uint128 b) internal pure returns (uint128) {
            return div(a, b, "ac");
        }
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         *
         * _Available since v2.4.0._
         */
        function div(
            uint128 a,
            uint128 b,
            string memory errorMessage
        ) internal pure returns (uint128) {
            // Solidity only automatically asserts when dividing by 0
            require(b > 0, errorMessage);
            uint128 c = a / b;
            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
            return c;
        }
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function mod(uint128 a, uint128 b) internal pure returns (uint128) {
            return mod(a, b, "ad");
        }
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts with custom message when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         *
         * _Available since v2.4.0._
         */
        function mod(
            uint128 a,
            uint128 b,
            string memory errorMessage
        ) internal pure returns (uint128) {
            require(b != 0, errorMessage);
            return a % b;
        }
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    /**
     * @dev Wrappers over Solidity's uintXX casting operators with added overflow
     * checks.
     *
     * Downcasting from uint256 in Solidity does not revert on overflow. This can
     * easily result in undesired exploitation or bugs, since developers usually
     * assume that overflows raise errors. `SafeCast` restores this intuition by
     * reverting the transaction when such an operation overflows.
     *
     * Using this library instead of the unchecked operations eliminates an entire
     * class of bugs, so it's recommended to use it always.
     *
     * Can be combined with {SafeMath} to extend it to smaller types, by performing
     * all math on `uint256` and then downcasting.
     *
     * _Available since v2.5.0._
     */
    library SafeCast {
        /**
         * @dev Returns the downcasted uint128 from uint256, reverting on
         * overflow (when the input is greater than largest uint128).
         *
         * Counterpart to Solidity's `uint128` operator.
         *
         * Requirements:
         *
         * - input must fit into 128 bits
         */
        function toUint128(uint256 value) internal pure returns (uint128) {
            require(value < 2**128, "16");
            return uint128(value);
        }
        /**
         * @dev Returns the downcasted uint64 from uint256, reverting on
         * overflow (when the input is greater than largest uint64).
         *
         * Counterpart to Solidity's `uint64` operator.
         *
         * Requirements:
         *
         * - input must fit into 64 bits
         */
        function toUint64(uint256 value) internal pure returns (uint64) {
            require(value < 2**64, "17");
            return uint64(value);
        }
        /**
         * @dev Returns the downcasted uint32 from uint256, reverting on
         * overflow (when the input is greater than largest uint32).
         *
         * Counterpart to Solidity's `uint32` operator.
         *
         * Requirements:
         *
         * - input must fit into 32 bits
         */
        function toUint32(uint256 value) internal pure returns (uint32) {
            require(value < 2**32, "18");
            return uint32(value);
        }
        /**
         * @dev Returns the downcasted uint16 from uint256, reverting on
         * overflow (when the input is greater than largest uint16).
         *
         * Counterpart to Solidity's `uint16` operator.
         *
         * Requirements:
         *
         * - input must fit into 16 bits
         */
        function toUint16(uint256 value) internal pure returns (uint16) {
            require(value < 2**16, "19");
            return uint16(value);
        }
        /**
         * @dev Returns the downcasted uint8 from uint256, reverting on
         * overflow (when the input is greater than largest uint8).
         *
         * Counterpart to Solidity's `uint8` operator.
         *
         * Requirements:
         *
         * - input must fit into 8 bits.
         */
        function toUint8(uint256 value) internal pure returns (uint8) {
            require(value < 2**8, "1a");
            return uint8(value);
        }
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    import "./IERC20.sol";
    import "./Bytes.sol";
    library Utils {
        /// @notice Returns lesser of two values
        function minU32(uint32 a, uint32 b) internal pure returns (uint32) {
            return a < b ? a : b;
        }
        /// @notice Returns lesser of two values
        function minU64(uint64 a, uint64 b) internal pure returns (uint64) {
            return a < b ? a : b;
        }
        /// @notice Returns lesser of two values
        function minU128(uint128 a, uint128 b) internal pure returns (uint128) {
            return a < b ? a : b;
        }
        /// @notice Recovers signer's address from ethereum signature for given message
        /// @param _signature 65 bytes concatenated. R (32) + S (32) + V (1)
        /// @param _messageHash signed message hash.
        /// @return address of the signer
        /// NOTE: will revert if signature is invalid
        function recoverAddressFromEthSignature(bytes memory _signature, bytes32 _messageHash)
            internal
            pure
            returns (address)
        {
            require(_signature.length == 65, "P"); // incorrect signature length
            bytes32 signR;
            bytes32 signS;
            uint8 signV;
            assembly {
                signR := mload(add(_signature, 32))
                signS := mload(add(_signature, 64))
                signV := byte(0, mload(add(_signature, 96)))
            }
            address recoveredAddress = ecrecover(_messageHash, signV, signR, signS);
            require(recoveredAddress != address(0), "p4"); // invalid signature
            return recoveredAddress;
        }
        /// @notice Returns new_hash = hash(old_hash + bytes)
        function concatHash(bytes32 _hash, bytes memory _bytes) internal pure returns (bytes32) {
            bytes32 result;
            assembly {
                let bytesLen := add(mload(_bytes), 32)
                mstore(_bytes, _hash)
                result := keccak256(_bytes, bytesLen)
            }
            return result;
        }
        function hashBytesToBytes20(bytes memory _bytes) internal pure returns (bytes20) {
            return bytes20(uint160(uint256(keccak256(_bytes))));
        }
        function getChainId() internal pure returns (uint256) {
            uint256 chainId;
            assembly {
                chainId := chainid()
            }
            return chainId;
        }
    }
    pragma solidity ^0.7.0;
    pragma experimental ABIEncoderV2;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    // solhint-disable max-states-count
    import "./IERC20.sol";
    import "./Governance.sol";
    import "./Verifier.sol";
    import "./Operations.sol";
    import "./NFTFactory.sol";
    import "./AdditionalZkSync.sol";
    /// @title zkSync storage contract
    /// @author Matter Labs
    contract Storage {
        /// @dev Flag indicates that upgrade preparation status is active
        /// @dev Will store false in case of not active upgrade mode
        bool internal upgradePreparationActive;
        /// @dev Upgrade preparation activation timestamp (as seconds since unix epoch)
        /// @dev Will be equal to zero in case of not active upgrade mode
        uint256 internal upgradePreparationActivationTime;
        /// @dev Verifier contract. Used to verify block proof and exit proof
        Verifier internal verifier;
        /// @dev Governance contract. Contains the governor (the owner) of whole system, validators list, possible tokens list
        Governance internal governance;
        uint8 internal constant FILLED_GAS_RESERVE_VALUE = 0xff; // we use it to set gas revert value so slot will not be emptied with 0 balance
        struct PendingBalance {
            uint128 balanceToWithdraw;
            uint8 gasReserveValue; // gives user opportunity to fill storage slot with nonzero value
        }
        /// @dev Root-chain balances (per owner and token id, see packAddressAndTokenId) to withdraw
        mapping(bytes22 => PendingBalance) internal pendingBalances;
        // @dev Pending withdrawals are not used in this version
        struct PendingWithdrawalDEPRECATED {
            address to;
            uint16 tokenId;
        }
        mapping(uint32 => PendingWithdrawalDEPRECATED) internal pendingWithdrawalsDEPRECATED;
        uint32 internal firstPendingWithdrawalIndexDEPRECATED;
        uint32 internal numberOfPendingWithdrawalsDEPRECATED;
        /// @dev Total number of executed blocks i.e. blocks[totalBlocksExecuted] points at the latest executed block (block 0 is genesis)
        uint32 public totalBlocksExecuted;
        /// @notice Total number of committed blocks i.e. blocks[totalBlocksCommitted] points at the latest committed block
        uint32 public totalBlocksCommitted;
        /// @Old rollup block stored data - not used in current version
        /// @member validator Block producer
        /// @member committedAtBlock ETH block number at which this block was committed
        /// @member cumulativeOnchainOperations Total number of operations in this and all previous blocks
        /// @member priorityOperations Total number of priority operations for this block
        /// @member commitment Hash of the block circuit commitment
        /// @member stateRoot New tree root hash
        ///
        /// Consider memory alignment when changing field order: https://solidity.readthedocs.io/en/v0.4.21/miscellaneous.html
        struct BlockDEPRECATED {
            uint32 committedAtBlock;
            uint64 priorityOperations;
            uint32 chunks;
            bytes32 withdrawalsDataHash; // can be restricted to 16 bytes to reduce number of required storage slots
            bytes32 commitment;
            bytes32 stateRoot;
        }
        mapping(uint32 => BlockDEPRECATED) internal blocksDEPRECATED;
        /// @dev Flag indicates that a user has exited in the exodus mode certain token balance (per account id and tokenId)
        mapping(uint32 => mapping(uint32 => bool)) internal performedExodus;
        /// @dev Flag indicates that exodus (mass exit) mode is triggered
        /// @dev Once it was raised, it can not be cleared again, and all users must exit
        bool public exodusMode;
        /// @dev User authenticated fact hashes for some nonce.
        mapping(address => mapping(uint32 => bytes32)) public authFacts;
        /// @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 PriorityOperationDEPRECATED {
            Operations.OpType opType;
            bytes pubData;
            uint256 expirationBlock;
        }
        /// @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 => PriorityOperationDEPRECATED) internal priorityRequestsDEPRECATED;
        /// @dev First open priority request id
        uint64 public firstPriorityRequestId;
        /// @dev Total number of requests
        uint64 public totalOpenPriorityRequests;
        /// @dev Total number of committed requests.
        /// @dev Used in checks: if the request matches the operation on Rollup contract and if provided number of requests is not too big
        uint64 internal totalCommittedPriorityRequests;
        /// @notice Packs address and token id into single word to use as a key in balances mapping
        function packAddressAndTokenId(address _address, uint16 _tokenId) internal pure returns (bytes22) {
            return bytes22((uint176(_address) | (uint176(_tokenId) << 160)));
        }
        /// @Rollup block stored data
        /// @member blockNumber Rollup block number
        /// @member priorityOperations Number of priority operations processed
        /// @member pendingOnchainOperationsHash Hash of all operations that must be processed after verify
        /// @member timestamp Rollup block timestamp, have the same format as Ethereum block constant
        /// @member stateHash Root hash of the rollup state
        /// @member commitment Verified input for the zkSync circuit
        struct StoredBlockInfo {
            uint32 blockNumber;
            uint64 priorityOperations;
            bytes32 pendingOnchainOperationsHash;
            uint256 timestamp;
            bytes32 stateHash;
            bytes32 commitment;
        }
        /// @notice Returns the keccak hash of the ABI-encoded StoredBlockInfo
        function hashStoredBlockInfo(StoredBlockInfo memory _storedBlockInfo) internal pure returns (bytes32) {
            return keccak256(abi.encode(_storedBlockInfo));
        }
        /// @dev Stored hashed StoredBlockInfo for some block number
        mapping(uint32 => bytes32) public storedBlockHashes;
        /// @dev Total blocks proven.
        uint32 public totalBlocksProven;
        /// @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;
        }
        /// @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;
        /// @dev Timer for authFacts entry reset (address, nonce -> timer).
        /// @dev Used when user wants to reset `authFacts` for some nonce.
        mapping(address => mapping(uint32 => uint256)) public authFactsResetTimer;
        mapping(uint32 => address) internal withdrawnNFTs;
        mapping(uint32 => Operations.WithdrawNFT) internal pendingWithdrawnNFTs;
        AdditionalZkSync internal additionalZkSync;
        /// @dev Upgrade notice period, possibly shorten by the security council
        uint256 internal approvedUpgradeNoticePeriod;
        /// @dev Upgrade start timestamp (as seconds since unix epoch)
        /// @dev Will be equal to zero in case of not active upgrade mode
        uint256 internal upgradeStartTimestamp;
        /// @dev Stores boolean flags which means the confirmations of the upgrade for each member of security council
        /// @dev Will store zeroes in case of not active upgrade mode
        mapping(uint256 => bool) internal securityCouncilApproves;
        uint256 internal numberOfApprovalsFromSecurityCouncil;
        /// @notice Checks that current state not is exodus mode
        function requireActive() internal view {
            require(!exodusMode, "L"); // exodus mode activated
        }
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    /// @title zkSync configuration constants
    /// @author Matter Labs
    contract Config {
        /// @dev ERC20 tokens and ETH withdrawals gas limit, used only for complete withdrawals
        uint256 internal constant WITHDRAWAL_GAS_LIMIT = 100000;
        /// @dev NFT withdrawals gas limit, used only for complete withdrawals
        uint256 internal constant WITHDRAWAL_NFT_GAS_LIMIT = 300000;
        /// @dev Bytes in one chunk
        uint8 internal constant CHUNK_BYTES = 10;
        /// @dev zkSync address length
        uint8 internal constant ADDRESS_BYTES = 20;
        uint8 internal constant PUBKEY_HASH_BYTES = 20;
        /// @dev Public key bytes length
        uint8 internal constant PUBKEY_BYTES = 32;
        /// @dev Ethereum signature r/s bytes length
        uint8 internal constant ETH_SIGN_RS_BYTES = 32;
        /// @dev Success flag bytes length
        uint8 internal constant SUCCESS_FLAG_BYTES = 1;
        /// @dev Max amount of tokens registered in the network (excluding ETH, which is hardcoded as tokenId = 0)
        uint32 internal constant MAX_AMOUNT_OF_REGISTERED_TOKENS = 1023;
        /// @dev Max account id that could be registered in the network
        uint32 internal constant MAX_ACCOUNT_ID = 16777215;
        /// @dev Expected average period of block creation
        uint256 internal constant BLOCK_PERIOD = 15 seconds;
        /// @dev ETH blocks verification expectation
        /// @dev Blocks can be reverted if they are not verified for at least EXPECT_VERIFICATION_IN.
        /// @dev If set to 0 validator can revert blocks at any time.
        uint256 internal constant EXPECT_VERIFICATION_IN = 0 hours / BLOCK_PERIOD;
        uint256 internal constant NOOP_BYTES = 1 * CHUNK_BYTES;
        uint256 internal constant DEPOSIT_BYTES = 6 * CHUNK_BYTES;
        uint256 internal constant MINT_NFT_BYTES = 5 * CHUNK_BYTES;
        uint256 internal constant TRANSFER_TO_NEW_BYTES = 6 * CHUNK_BYTES;
        uint256 internal constant PARTIAL_EXIT_BYTES = 6 * CHUNK_BYTES;
        uint256 internal constant TRANSFER_BYTES = 2 * CHUNK_BYTES;
        uint256 internal constant FORCED_EXIT_BYTES = 6 * CHUNK_BYTES;
        uint256 internal constant WITHDRAW_NFT_BYTES = 10 * CHUNK_BYTES;
        /// @dev Full exit operation length
        uint256 internal constant FULL_EXIT_BYTES = 11 * CHUNK_BYTES;
        /// @dev ChangePubKey operation length
        uint256 internal constant CHANGE_PUBKEY_BYTES = 6 * CHUNK_BYTES;
        /// @dev Expiration delta for priority request to be satisfied (in seconds)
        /// @dev NOTE: Priority expiration should be > (EXPECT_VERIFICATION_IN * BLOCK_PERIOD)
        /// @dev otherwise incorrect block with priority op could not be reverted.
        uint256 internal constant PRIORITY_EXPIRATION_PERIOD = 14 days;
        /// @dev Expiration delta for priority request to be satisfied (in ETH blocks)
        uint256 internal constant PRIORITY_EXPIRATION =
            PRIORITY_EXPIRATION_PERIOD/BLOCK_PERIOD;
        /// @dev Maximum number of priority request to clear during verifying the block
        /// @dev Cause deleting storage slots cost 5k gas per each slot it's unprofitable to clear too many slots
        /// @dev Value based on the assumption of ~750k gas cost of verifying and 5 used storage slots per PriorityOperation structure
        uint64 internal constant MAX_PRIORITY_REQUESTS_TO_DELETE_IN_VERIFY = 6;
        /// @dev Reserved time for users to send full exit priority operation in case of an upgrade (in seconds)
        uint256 internal constant MASS_FULL_EXIT_PERIOD = 5 days;
        /// @dev Reserved time for users to withdraw funds from full exit priority operation in case of an upgrade (in seconds)
        uint256 internal constant TIME_TO_WITHDRAW_FUNDS_FROM_FULL_EXIT = 2 days;
        /// @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 internal constant UPGRADE_NOTICE_PERIOD =
            MASS_FULL_EXIT_PERIOD+PRIORITY_EXPIRATION_PERIOD+TIME_TO_WITHDRAW_FUNDS_FROM_FULL_EXIT;
        /// @dev Timestamp - seconds since unix epoch
        uint256 internal constant COMMIT_TIMESTAMP_NOT_OLDER = 24 hours;
        /// @dev Maximum available error between real commit block timestamp and analog used in the verifier (in seconds)
        /// @dev Must be used cause miner's `block.timestamp` value can differ on some small value (as we know - 15 seconds)
        uint256 internal constant COMMIT_TIMESTAMP_APPROXIMATION_DELTA = 15 minutes;
        /// @dev Bit mask to apply for verifier public input before verifying.
        uint256 internal constant INPUT_MASK = 14474011154664524427946373126085988481658748083205070504932198000989141204991;
        /// @dev Auth fact reset timelock.
        uint256 internal constant AUTH_FACT_RESET_TIMELOCK = 1 days;
        /// @dev Max deposit of ERC20 token that is possible to deposit
        uint128 internal constant MAX_DEPOSIT_AMOUNT = 20282409603651670423947251286015;
        uint32 internal constant SPECIAL_ACCOUNT_ID = 16777215;
        address internal constant SPECIAL_ACCOUNT_ADDRESS = address(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF);
        uint32 internal constant SPECIAL_NFT_TOKEN_ID = 2147483646;
        uint32 internal constant MAX_FUNGIBLE_TOKEN_ID = 65535;
        uint256 internal constant SECURITY_COUNCIL_MEMBERS_NUMBER = 15;
        string internal constant name = "ZkSync";
        string internal constant version = "1.0";
        bytes32 internal constant EIP712_DOMAIN_TYPEHASH =
            keccak256("EIP712Domain(string name,string version,uint256 chainId)");
        bytes32 internal constant EIP712_CHANGEPUBKEY_TYPEHASH =
            keccak256("ChangePubKey(bytes20 pubKeyHash,uint32 nonce,uint32 accountId)");
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    import "./Upgradeable.sol";
    import "./Operations.sol";
    /// @title zkSync events
    /// @author Matter Labs
    interface Events {
        /// @notice Event emitted when a block is committed
        event BlockCommit(uint32 indexed blockNumber);
        /// @notice Event emitted when a block is verified
        event BlockVerification(uint32 indexed blockNumber);
        /// @notice Event emitted when user funds are withdrawn from the zkSync state and contract
        event Withdrawal(address indexed owner, uint16 indexed tokenId, uint128 amount);
        /// @notice Event emitted when user funds are withdrawn from the zkSync state but not from contract
        event WithdrawalPending(
            uint16 indexed tokenId,
            address indexed recipient,
            uint128 amount,
            Operations.WithdrawalType withdrawalType
        );
        /// @notice Event emitted when user NFT is withdrawn from the zkSync state and contract
        event WithdrawalNFT(uint32 indexed tokenId);
        /// @notice Event emitted when user NFT is withdrawn from the zkSync state but not from contract
        event WithdrawalNFTPending(uint32 indexed tokenId);
        /// @notice Event emitted when user funds are deposited to the zkSync contract
        event Deposit(uint16 indexed tokenId, uint128 amount);
        /// @notice Event emitted when user sends a authentication fact (e.g. pub-key hash)
        event FactAuth(address indexed sender, uint32 nonce, bytes fact);
        /// @notice Event emitted when blocks are reverted
        event BlocksRevert(uint32 totalBlocksVerified, uint32 totalBlocksCommitted);
        /// @notice Exodus mode entered event
        event ExodusMode();
        /// @notice New priority request event. Emitted when a request is placed into mapping
        event NewPriorityRequest(
            address sender,
            uint64 serialId,
            Operations.OpType opType,
            bytes pubData,
            uint256 expirationBlock
        );
        /// @notice Deposit committed event.
        event DepositCommit(
            uint32 indexed zkSyncBlockId,
            uint32 indexed accountId,
            address owner,
            uint16 indexed tokenId,
            uint128 amount
        );
        /// @notice Full exit committed event.
        event FullExitCommit(
            uint32 indexed zkSyncBlockId,
            uint32 indexed accountId,
            address owner,
            uint16 indexed tokenId,
            uint128 amount
        );
        /// @notice Approve cut of upgrade notice period by addr
        event ApproveCutUpgradeNoticePeriod(address indexed addr);
        /// @notice Notice period changed
        event NoticePeriodChange(uint256 newNoticePeriod);
    }
    /// @title Upgrade events
    /// @author Matter Labs
    interface UpgradeEvents {
        /// @notice Event emitted when new upgradeable contract is added to upgrade gatekeeper's list of managed contracts
        event NewUpgradable(uint256 indexed versionId, address indexed upgradeable);
        /// @notice Upgrade mode enter event
        event NoticePeriodStart(
            uint256 indexed versionId,
            address[] newTargets,
            uint256 noticePeriod // notice period (in seconds)
        );
        /// @notice Upgrade mode cancel event
        event UpgradeCancel(uint256 indexed versionId);
        /// @notice Upgrade mode preparation status event
        event PreparationStart(uint256 indexed versionId);
        /// @notice Upgrade mode complete event
        event UpgradeComplete(uint256 indexed versionId, address[] newTargets);
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    // Functions named bytesToX, except bytesToBytes20, where X is some type of size N < 32 (size of one word)
    // implements the following algorithm:
    // f(bytes memory input, uint offset) -> X out
    // where byte representation of out is N bytes from input at the given offset
    // 1) We compute memory location of the word W such that last N bytes of W is input[offset..offset+N]
    // W_address = input + 32 (skip stored length of bytes) + offset - (32 - N) == input + offset + N
    // 2) We load W from memory into out, last N bytes of W are placed into out
    library Bytes {
        function toBytesFromUInt16(uint16 self) internal pure returns (bytes memory _bts) {
            return toBytesFromUIntTruncated(uint256(self), 2);
        }
        function toBytesFromUInt24(uint24 self) internal pure returns (bytes memory _bts) {
            return toBytesFromUIntTruncated(uint256(self), 3);
        }
        function toBytesFromUInt32(uint32 self) internal pure returns (bytes memory _bts) {
            return toBytesFromUIntTruncated(uint256(self), 4);
        }
        function toBytesFromUInt128(uint128 self) internal pure returns (bytes memory _bts) {
            return toBytesFromUIntTruncated(uint256(self), 16);
        }
        // Copies 'len' lower bytes from 'self' into a new 'bytes memory'.
        // Returns the newly created 'bytes memory'. The returned bytes will be of length 'len'.
        function toBytesFromUIntTruncated(uint256 self, uint8 byteLength) private pure returns (bytes memory bts) {
            require(byteLength <= 32, "Q");
            bts = new bytes(byteLength);
            // Even though the bytes will allocate a full word, we don't want
            // any potential garbage bytes in there.
            uint256 data = self << ((32 - byteLength) * 8);
            assembly {
                mstore(
                    add(bts, 32), // BYTES_HEADER_SIZE
                    data
                )
            }
        }
        // Copies 'self' into a new 'bytes memory'.
        // Returns the newly created 'bytes memory'. The returned bytes will be of length '20'.
        function toBytesFromAddress(address self) internal pure returns (bytes memory bts) {
            bts = toBytesFromUIntTruncated(uint256(self), 20);
        }
        // See comment at the top of this file for explanation of how this function works.
        // NOTE: theoretically possible overflow of (_start + 20)
        function bytesToAddress(bytes memory self, uint256 _start) internal pure returns (address addr) {
            uint256 offset = _start + 20;
            require(self.length >= offset, "R");
            assembly {
                addr := mload(add(self, offset))
            }
        }
        // Reasoning about why this function works is similar to that of other similar functions, except NOTE below.
        // NOTE: that bytes1..32 is stored in the beginning of the word unlike other primitive types
        // NOTE: theoretically possible overflow of (_start + 20)
        function bytesToBytes20(bytes memory self, uint256 _start) internal pure returns (bytes20 r) {
            require(self.length >= (_start + 20), "S");
            assembly {
                r := mload(add(add(self, 0x20), _start))
            }
        }
        // See comment at the top of this file for explanation of how this function works.
        // NOTE: theoretically possible overflow of (_start + 0x2)
        function bytesToUInt16(bytes memory _bytes, uint256 _start) internal pure returns (uint16 r) {
            uint256 offset = _start + 0x2;
            require(_bytes.length >= offset, "T");
            assembly {
                r := mload(add(_bytes, offset))
            }
        }
        // See comment at the top of this file for explanation of how this function works.
        // NOTE: theoretically possible overflow of (_start + 0x3)
        function bytesToUInt24(bytes memory _bytes, uint256 _start) internal pure returns (uint24 r) {
            uint256 offset = _start + 0x3;
            require(_bytes.length >= offset, "U");
            assembly {
                r := mload(add(_bytes, offset))
            }
        }
        // NOTE: theoretically possible overflow of (_start + 0x4)
        function bytesToUInt32(bytes memory _bytes, uint256 _start) internal pure returns (uint32 r) {
            uint256 offset = _start + 0x4;
            require(_bytes.length >= offset, "V");
            assembly {
                r := mload(add(_bytes, offset))
            }
        }
        // NOTE: theoretically possible overflow of (_start + 0x10)
        function bytesToUInt128(bytes memory _bytes, uint256 _start) internal pure returns (uint128 r) {
            uint256 offset = _start + 0x10;
            require(_bytes.length >= offset, "W");
            assembly {
                r := mload(add(_bytes, offset))
            }
        }
        // See comment at the top of this file for explanation of how this function works.
        // NOTE: theoretically possible overflow of (_start + 0x14)
        function bytesToUInt160(bytes memory _bytes, uint256 _start) internal pure returns (uint160 r) {
            uint256 offset = _start + 0x14;
            require(_bytes.length >= offset, "X");
            assembly {
                r := mload(add(_bytes, offset))
            }
        }
        // NOTE: theoretically possible overflow of (_start + 0x20)
        function bytesToBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32 r) {
            uint256 offset = _start + 0x20;
            require(_bytes.length >= offset, "Y");
            assembly {
                r := mload(add(_bytes, offset))
            }
        }
        // Original source code: https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol#L228
        // Get slice from bytes arrays
        // Returns the newly created 'bytes memory'
        // NOTE: theoretically possible overflow of (_start + _length)
        function slice(
            bytes memory _bytes,
            uint256 _start,
            uint256 _length
        ) internal pure returns (bytes memory) {
            require(_bytes.length >= (_start + _length), "Z"); // bytes length is less then start byte + length bytes
            bytes memory tempBytes = new bytes(_length);
            if (_length != 0) {
                assembly {
                    let slice_curr := add(tempBytes, 0x20)
                    let slice_end := add(slice_curr, _length)
                    for {
                        let array_current := add(_bytes, add(_start, 0x20))
                    } lt(slice_curr, slice_end) {
                        slice_curr := add(slice_curr, 0x20)
                        array_current := add(array_current, 0x20)
                    } {
                        mstore(slice_curr, mload(array_current))
                    }
                }
            }
            return tempBytes;
        }
        /// Reads byte stream
        /// @return newOffset - offset + amount of bytes read
        /// @return data - actually read data
        // NOTE: theoretically possible overflow of (_offset + _length)
        function read(
            bytes memory _data,
            uint256 _offset,
            uint256 _length
        ) internal pure returns (uint256 newOffset, bytes memory data) {
            data = slice(_data, _offset, _length);
            newOffset = _offset + _length;
        }
        // NOTE: theoretically possible overflow of (_offset + 1)
        function readBool(bytes memory _data, uint256 _offset) internal pure returns (uint256 newOffset, bool r) {
            newOffset = _offset + 1;
            r = uint8(_data[_offset]) != 0;
        }
        // NOTE: theoretically possible overflow of (_offset + 1)
        function readUint8(bytes memory _data, uint256 _offset) internal pure returns (uint256 newOffset, uint8 r) {
            newOffset = _offset + 1;
            r = uint8(_data[_offset]);
        }
        // NOTE: theoretically possible overflow of (_offset + 2)
        function readUInt16(bytes memory _data, uint256 _offset) internal pure returns (uint256 newOffset, uint16 r) {
            newOffset = _offset + 2;
            r = bytesToUInt16(_data, _offset);
        }
        // NOTE: theoretically possible overflow of (_offset + 3)
        function readUInt24(bytes memory _data, uint256 _offset) internal pure returns (uint256 newOffset, uint24 r) {
            newOffset = _offset + 3;
            r = bytesToUInt24(_data, _offset);
        }
        // NOTE: theoretically possible overflow of (_offset + 4)
        function readUInt32(bytes memory _data, uint256 _offset) internal pure returns (uint256 newOffset, uint32 r) {
            newOffset = _offset + 4;
            r = bytesToUInt32(_data, _offset);
        }
        // NOTE: theoretically possible overflow of (_offset + 16)
        function readUInt128(bytes memory _data, uint256 _offset) internal pure returns (uint256 newOffset, uint128 r) {
            newOffset = _offset + 16;
            r = bytesToUInt128(_data, _offset);
        }
        // NOTE: theoretically possible overflow of (_offset + 20)
        function readUInt160(bytes memory _data, uint256 _offset) internal pure returns (uint256 newOffset, uint160 r) {
            newOffset = _offset + 20;
            r = bytesToUInt160(_data, _offset);
        }
        // NOTE: theoretically possible overflow of (_offset + 20)
        function readAddress(bytes memory _data, uint256 _offset) internal pure returns (uint256 newOffset, address r) {
            newOffset = _offset + 20;
            r = bytesToAddress(_data, _offset);
        }
        // NOTE: theoretically possible overflow of (_offset + 20)
        function readBytes20(bytes memory _data, uint256 _offset) internal pure returns (uint256 newOffset, bytes20 r) {
            newOffset = _offset + 20;
            r = bytesToBytes20(_data, _offset);
        }
        // NOTE: theoretically possible overflow of (_offset + 32)
        function readBytes32(bytes memory _data, uint256 _offset) internal pure returns (uint256 newOffset, bytes32 r) {
            newOffset = _offset + 32;
            r = bytesToBytes32(_data, _offset);
        }
        /// Trim bytes into single word
        function trim(bytes memory _data, uint256 _newLength) internal pure returns (uint256 r) {
            require(_newLength <= 0x20, "10"); // new_length is longer than word
            require(_data.length >= _newLength, "11"); // data is to short
            uint256 a;
            assembly {
                a := mload(add(_data, 0x20)) // load bytes into uint256
            }
            return a >> ((0x20 - _newLength) * 8);
        }
        // Helper function for hex conversion.
        function halfByteToHex(bytes1 _byte) internal pure returns (bytes1 _hexByte) {
            require(uint8(_byte) < 0x10, "hbh11"); // half byte's value is out of 0..15 range.
            // "FEDCBA9876543210" ASCII-encoded, shifted and automatically truncated.
            return bytes1(uint8(0x66656463626139383736353433323130 >> (uint8(_byte) * 8)));
        }
        // Convert bytes to ASCII hex representation
        function bytesToHexASCIIBytes(bytes memory _input) internal pure returns (bytes memory _output) {
            bytes memory outStringBytes = new bytes(_input.length * 2);
            // code in `assembly` construction is equivalent of the next code:
            // for (uint i = 0; i < _input.length; ++i) {
            //     outStringBytes[i*2] = halfByteToHex(_input[i] >> 4);
            //     outStringBytes[i*2+1] = halfByteToHex(_input[i] & 0x0f);
            // }
            assembly {
                let input_curr := add(_input, 0x20)
                let input_end := add(input_curr, mload(_input))
                for {
                    let out_curr := add(outStringBytes, 0x20)
                } lt(input_curr, input_end) {
                    input_curr := add(input_curr, 0x01)
                    out_curr := add(out_curr, 0x02)
                } {
                    let curr_input_byte := shr(0xf8, mload(input_curr))
                    // here outStringByte from each half of input byte calculates by the next:
                    //
                    // "FEDCBA9876543210" ASCII-encoded, shifted and automatically truncated.
                    // outStringByte = byte (uint8 (0x66656463626139383736353433323130 >> (uint8 (_byteHalf) * 8)))
                    mstore(
                        out_curr,
                        shl(0xf8, shr(mul(shr(0x04, curr_input_byte), 0x08), 0x66656463626139383736353433323130))
                    )
                    mstore(
                        add(out_curr, 0x01),
                        shl(0xf8, shr(mul(and(0x0f, curr_input_byte), 0x08), 0x66656463626139383736353433323130))
                    )
                }
            }
            return outStringBytes;
        }
    }
    pragma solidity ^0.7.0;
    pragma experimental ABIEncoderV2;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    import "./Bytes.sol";
    import "./Utils.sol";
    /// @title zkSync operations tools
    library Operations {
        // Circuit ops and their pubdata (chunks * bytes)
        /// @notice zkSync circuit operation type
        enum OpType {
            Noop,
            Deposit,
            TransferToNew,
            PartialExit,
            _CloseAccount, // used for correct op id offset
            Transfer,
            FullExit,
            ChangePubKey,
            ForcedExit,
            MintNFT,
            WithdrawNFT,
            Swap
        }
        /// @notice zkSync withdrawal types
        enum WithdrawalType {
            PartialExit,
            ForcedExit,
            FullExit
        }
        // Byte lengths
        uint8 internal constant OP_TYPE_BYTES = 1;
        uint8 internal constant TOKEN_BYTES = 4;
        uint8 internal constant PUBKEY_BYTES = 32;
        uint8 internal constant NONCE_BYTES = 4;
        uint8 internal constant PUBKEY_HASH_BYTES = 20;
        uint8 internal constant ADDRESS_BYTES = 20;
        uint8 internal constant CONTENT_HASH_BYTES = 32;
        /// @dev Packed fee bytes lengths
        uint8 internal constant FEE_BYTES = 2;
        /// @dev zkSync account id bytes lengths
        uint8 internal constant ACCOUNT_ID_BYTES = 4;
        /// @dev zkSync nft serial id bytes lengths
        uint8 internal constant NFT_SERIAL_ID_BYTES = 4;
        uint8 internal constant AMOUNT_BYTES = 16;
        /// @dev Signature (for example full exit signature) bytes length
        uint8 internal constant SIGNATURE_BYTES = 64;
        // Deposit pubdata
        struct Deposit {
            // uint8 opType
            uint32 accountId;
            uint32 tokenId;
            uint128 amount;
            address owner;
        }
        uint256 internal constant PACKED_DEPOSIT_PUBDATA_BYTES =
            OP_TYPE_BYTES + ACCOUNT_ID_BYTES + TOKEN_BYTES + AMOUNT_BYTES + ADDRESS_BYTES;
        /// Deserialize deposit pubdata
        function readDepositPubdata(bytes memory _data) internal pure returns (Deposit memory parsed) {
            // NOTE: there is no check that variable sizes are same as constants (i.e. TOKEN_BYTES), fix if possible.
            uint256 offset = OP_TYPE_BYTES;
            (offset, parsed.accountId) = Bytes.readUInt32(_data, offset); // accountId
            (offset, parsed.tokenId) = Bytes.readUInt32(_data, offset); // tokenId
            (offset, parsed.amount) = Bytes.readUInt128(_data, offset); // amount
            (offset, parsed.owner) = Bytes.readAddress(_data, offset); // owner
            require(offset == PACKED_DEPOSIT_PUBDATA_BYTES, "N"); // reading invalid deposit pubdata size
        }
        /// Serialize deposit pubdata
        function writeDepositPubdataForPriorityQueue(Deposit memory op) internal pure returns (bytes memory buf) {
            buf = abi.encodePacked(
                uint8(OpType.Deposit),
                bytes4(0), // accountId (ignored) (update when ACCOUNT_ID_BYTES is changed)
                op.tokenId, // tokenId
                op.amount, // amount
                op.owner // owner
            );
        }
        /// @notice Write deposit pubdata for priority queue check.
        function checkDepositInPriorityQueue(Deposit memory op, bytes20 hashedPubdata) internal pure returns (bool) {
            return Utils.hashBytesToBytes20(writeDepositPubdataForPriorityQueue(op)) == hashedPubdata;
        }
        // FullExit pubdata
        struct FullExit {
            // uint8 opType
            uint32 accountId;
            address owner;
            uint32 tokenId;
            uint128 amount;
            uint32 nftCreatorAccountId;
            address nftCreatorAddress;
            uint32 nftSerialId;
            bytes32 nftContentHash;
        }
        uint256 public constant PACKED_FULL_EXIT_PUBDATA_BYTES =
            OP_TYPE_BYTES +
                ACCOUNT_ID_BYTES +
                ADDRESS_BYTES +
                TOKEN_BYTES +
                AMOUNT_BYTES +
                ACCOUNT_ID_BYTES +
                ADDRESS_BYTES +
                NFT_SERIAL_ID_BYTES +
                CONTENT_HASH_BYTES;
        function readFullExitPubdata(bytes memory _data) internal pure returns (FullExit memory parsed) {
            // NOTE: there is no check that variable sizes are same as constants (i.e. TOKEN_BYTES), fix if possible.
            uint256 offset = OP_TYPE_BYTES;
            (offset, parsed.accountId) = Bytes.readUInt32(_data, offset); // accountId
            (offset, parsed.owner) = Bytes.readAddress(_data, offset); // owner
            (offset, parsed.tokenId) = Bytes.readUInt32(_data, offset); // tokenId
            (offset, parsed.amount) = Bytes.readUInt128(_data, offset); // amount
            (offset, parsed.nftCreatorAccountId) = Bytes.readUInt32(_data, offset); // nftCreatorAccountId
            (offset, parsed.nftCreatorAddress) = Bytes.readAddress(_data, offset); // nftCreatorAddress
            (offset, parsed.nftSerialId) = Bytes.readUInt32(_data, offset); // nftSerialId
            (offset, parsed.nftContentHash) = Bytes.readBytes32(_data, offset); // nftContentHash
            require(offset == PACKED_FULL_EXIT_PUBDATA_BYTES, "O"); // reading invalid full exit pubdata size
        }
        function writeFullExitPubdataForPriorityQueue(FullExit memory op) internal pure returns (bytes memory buf) {
            buf = abi.encodePacked(
                uint8(OpType.FullExit),
                op.accountId, // accountId
                op.owner, // owner
                op.tokenId, // tokenId
                uint128(0), // amount -- ignored
                uint32(0), // nftCreatorAccountId -- ignored
                address(0), // nftCreatorAddress -- ignored
                uint32(0), // nftSerialId -- ignored
                bytes32(0) // nftContentHash -- ignored
            );
        }
        function checkFullExitInPriorityQueue(FullExit memory op, bytes20 hashedPubdata) internal pure returns (bool) {
            return Utils.hashBytesToBytes20(writeFullExitPubdataForPriorityQueue(op)) == hashedPubdata;
        }
        // PartialExit pubdata
        struct PartialExit {
            //uint8 opType; -- present in pubdata, ignored at serialization
            //uint32 accountId; -- present in pubdata, ignored at serialization
            uint32 tokenId;
            uint128 amount;
            //uint16 fee; -- present in pubdata, ignored at serialization
            address owner;
        }
        function readPartialExitPubdata(bytes memory _data) internal pure returns (PartialExit memory parsed) {
            // NOTE: there is no check that variable sizes are same as constants (i.e. TOKEN_BYTES), fix if possible.
            uint256 offset = OP_TYPE_BYTES + ACCOUNT_ID_BYTES; // opType + accountId (ignored)
            (offset, parsed.tokenId) = Bytes.readUInt32(_data, offset); // tokenId
            (offset, parsed.amount) = Bytes.readUInt128(_data, offset); // amount
            offset += FEE_BYTES; // fee (ignored)
            (offset, parsed.owner) = Bytes.readAddress(_data, offset); // owner
        }
        // ForcedExit pubdata
        struct ForcedExit {
            //uint8 opType; -- present in pubdata, ignored at serialization
            //uint32 initiatorAccountId; -- present in pubdata, ignored at serialization
            //uint32 targetAccountId; -- present in pubdata, ignored at serialization
            uint32 tokenId;
            uint128 amount;
            //uint16 fee; -- present in pubdata, ignored at serialization
            address target;
        }
        function readForcedExitPubdata(bytes memory _data) internal pure returns (ForcedExit memory parsed) {
            // NOTE: there is no check that variable sizes are same as constants (i.e. TOKEN_BYTES), fix if possible.
            uint256 offset = OP_TYPE_BYTES + ACCOUNT_ID_BYTES * 2; // opType + initiatorAccountId + targetAccountId (ignored)
            (offset, parsed.tokenId) = Bytes.readUInt32(_data, offset); // tokenId
            (offset, parsed.amount) = Bytes.readUInt128(_data, offset); // amount
            offset += FEE_BYTES; // fee (ignored)
            (offset, parsed.target) = Bytes.readAddress(_data, offset); // target
        }
        // ChangePubKey
        enum ChangePubkeyType {
            ECRECOVER,
            CREATE2,
            OldECRECOVER,
            ECRECOVERV2,
            EIP712
        }
        struct ChangePubKey {
            // uint8 opType; -- present in pubdata, ignored at serialization
            uint32 accountId;
            bytes20 pubKeyHash;
            address owner;
            uint32 nonce;
            //uint32 tokenId; -- present in pubdata, ignored at serialization
            //uint16 fee; -- present in pubdata, ignored at serialization
        }
        function readChangePubKeyPubdata(bytes memory _data) internal pure returns (ChangePubKey memory parsed) {
            uint256 offset = OP_TYPE_BYTES;
            (offset, parsed.accountId) = Bytes.readUInt32(_data, offset); // accountId
            (offset, parsed.pubKeyHash) = Bytes.readBytes20(_data, offset); // pubKeyHash
            (offset, parsed.owner) = Bytes.readAddress(_data, offset); // owner
            (offset, parsed.nonce) = Bytes.readUInt32(_data, offset); // nonce
        }
        struct WithdrawNFT {
            //uint8 opType; -- present in pubdata, ignored at serialization
            //uint32 accountId; -- present in pubdata, ignored at serialization
            uint32 creatorAccountId;
            address creatorAddress;
            uint32 serialId;
            bytes32 contentHash;
            address receiver;
            uint32 tokenId;
            //uint32 feeTokenId;
            //uint16 fee; -- present in pubdata, ignored at serialization
        }
        function readWithdrawNFTPubdata(bytes memory _data) internal pure returns (WithdrawNFT memory parsed) {
            uint256 offset = OP_TYPE_BYTES + ACCOUNT_ID_BYTES; // opType + accountId (ignored)
            (offset, parsed.creatorAccountId) = Bytes.readUInt32(_data, offset);
            (offset, parsed.creatorAddress) = Bytes.readAddress(_data, offset);
            (offset, parsed.serialId) = Bytes.readUInt32(_data, offset);
            (offset, parsed.contentHash) = Bytes.readBytes32(_data, offset);
            (offset, parsed.receiver) = Bytes.readAddress(_data, offset);
            (offset, parsed.tokenId) = Bytes.readUInt32(_data, offset);
        }
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    /// @title Interface of the upgradeable master contract (defines notice period duration and allows finish upgrade during preparation of it)
    /// @author Matter Labs
    interface UpgradeableMaster {
        /// @notice Notice period before activation preparation status of upgrade mode
        function getNoticePeriod() external returns (uint256);
        /// @notice Notifies contract that notice period started
        function upgradeNoticePeriodStarted() external;
        /// @notice Notifies contract that upgrade preparation status is activated
        function upgradePreparationStarted() external;
        /// @notice Notifies contract that upgrade canceled
        function upgradeCanceled() external;
        /// @notice Notifies contract that upgrade finishes
        function upgradeFinishes() external;
        /// @notice Checks that contract is ready for upgrade
        /// @return bool flag indicating that contract is ready for upgrade
        function isReadyForUpgrade() external returns (bool);
    }
    pragma solidity ^0.7.0;
    pragma experimental ABIEncoderV2;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    import "./ReentrancyGuard.sol";
    import "./SafeMath.sol";
    import "./SafeMathUInt128.sol";
    import "./SafeCast.sol";
    import "./Utils.sol";
    import "./Storage.sol";
    import "./Config.sol";
    import "./Events.sol";
    import "./Bytes.sol";
    import "./Operations.sol";
    import "./UpgradeableMaster.sol";
    /// @title zkSync additional main contract
    /// @author Matter Labs
    contract AdditionalZkSync is Storage, Config, Events, ReentrancyGuard {
        using SafeMath for uint256;
        using SafeMathUInt128 for uint128;
        function increaseBalanceToWithdraw(bytes22 _packedBalanceKey, uint128 _amount) internal {
            uint128 balance = pendingBalances[_packedBalanceKey].balanceToWithdraw;
            pendingBalances[_packedBalanceKey] = PendingBalance(balance.add(_amount), FILLED_GAS_RESERVE_VALUE);
        }
        /// @notice Withdraws token from ZkSync to root chain in case of exodus mode. User must provide proof that he owns funds
        /// @param _storedBlockInfo Last verified block
        /// @param _owner Owner of the account
        /// @param _accountId Id of the account in the tree
        /// @param _proof Proof
        /// @param _tokenId Verified token id
        /// @param _amount Amount for owner (must be total amount, not part of it)
        function performExodus(
            StoredBlockInfo calldata _storedBlockInfo,
            address _owner,
            uint32 _accountId,
            uint32 _tokenId,
            uint128 _amount,
            uint32 _nftCreatorAccountId,
            address _nftCreatorAddress,
            uint32 _nftSerialId,
            bytes32 _nftContentHash,
            uint256[] calldata _proof
        ) external nonReentrant {
            require(_accountId <= MAX_ACCOUNT_ID, "e");
            require(_accountId != SPECIAL_ACCOUNT_ID, "v");
            require(_tokenId < SPECIAL_NFT_TOKEN_ID, "T");
            require(exodusMode, "s"); // must be in exodus mode
            require(!performedExodus[_accountId][_tokenId], "t"); // already exited
            require(storedBlockHashes[totalBlocksExecuted] == hashStoredBlockInfo(_storedBlockInfo), "u"); // incorrect stored block info
            bool proofCorrect = verifier.verifyExitProof(
                _storedBlockInfo.stateHash,
                _accountId,
                _owner,
                _tokenId,
                _amount,
                _nftCreatorAccountId,
                _nftCreatorAddress,
                _nftSerialId,
                _nftContentHash,
                _proof
            );
            require(proofCorrect, "x");
            if (_tokenId <= MAX_FUNGIBLE_TOKEN_ID) {
                bytes22 packedBalanceKey = packAddressAndTokenId(_owner, uint16(_tokenId));
                increaseBalanceToWithdraw(packedBalanceKey, _amount);
                emit WithdrawalPending(uint16(_tokenId), _owner, _amount, Operations.WithdrawalType.FullExit);
            } else {
                require(_amount != 0, "Z"); // Unsupported nft amount
                Operations.WithdrawNFT memory withdrawNftOp = Operations.WithdrawNFT(
                    _nftCreatorAccountId,
                    _nftCreatorAddress,
                    _nftSerialId,
                    _nftContentHash,
                    _owner,
                    _tokenId
                );
                pendingWithdrawnNFTs[_tokenId] = withdrawNftOp;
                emit WithdrawalNFTPending(_tokenId);
            }
            performedExodus[_accountId][_tokenId] = true;
        }
        function cancelOutstandingDepositsForExodusMode(uint64 _n, bytes[] calldata _depositsPubdata)
            external
            nonReentrant
        {
            require(exodusMode, "8"); // exodus mode not active
            uint64 toProcess = Utils.minU64(totalOpenPriorityRequests, _n);
            require(toProcess > 0, "9"); // no deposits to process
            uint64 currentDepositIdx = 0;
            for (uint64 id = firstPriorityRequestId; id < firstPriorityRequestId + toProcess; ++id) {
                if (priorityRequests[id].opType == Operations.OpType.Deposit) {
                    bytes memory depositPubdata = _depositsPubdata[currentDepositIdx];
                    require(Utils.hashBytesToBytes20(depositPubdata) == priorityRequests[id].hashedPubData, "a");
                    ++currentDepositIdx;
                    Operations.Deposit memory op = Operations.readDepositPubdata(depositPubdata);
                    bytes22 packedBalanceKey = packAddressAndTokenId(op.owner, uint16(op.tokenId));
                    pendingBalances[packedBalanceKey].balanceToWithdraw += op.amount;
                }
                delete priorityRequests[id];
            }
            firstPriorityRequestId += toProcess;
            totalOpenPriorityRequests -= toProcess;
        }
        uint256 internal constant SECURITY_COUNCIL_THRESHOLD = 9;
        /// @notice processing new approval of decrease upgrade notice period time to zero
        /// @param addr address of the account that approved the reduction of the upgrade notice period to zero
        /// NOTE: does NOT revert if the address is not a security council member or number of approvals is already sufficient
        function approveCutUpgradeNoticePeriod(address addr) internal {
            address payable[SECURITY_COUNCIL_MEMBERS_NUMBER] memory SECURITY_COUNCIL_MEMBERS = [
                0xa2602ea835E03fb39CeD30B43d6b6EAf6aDe1769,0x9D5d6D4BaCCEDf6ECE1883456AA785dc996df607,0x002A5dc50bbB8d5808e418Aeeb9F060a2Ca17346,0x71E805aB236c945165b9Cd0bf95B9f2F0A0488c3,0x76C6cE74EAb57254E785d1DcC3f812D274bCcB11,0xFBfF3FF69D65A9103Bf4fdBf988f5271D12B3190,0xAfC2F2D803479A2AF3A72022D54cc0901a0ec0d6,0x4d1E3089042Ab3A93E03CA88B566b99Bd22438C6,0x19eD6cc20D44e5cF4Bb4894F50162F72402d8567,0x39415255619783A2E71fcF7d8f708A951d92e1b6,0x399a6a13D298CF3F41a562966C1a450136Ea52C2,0xee8AE1F1B4B1E1956C8Bda27eeBCE54Cf0bb5eaB,0xe7CCD4F3feA7df88Cf9B59B30f738ec1E049231f,0xA093284c707e207C36E3FEf9e0B6325fd9d0e33B,0x225d3822De44E58eE935440E0c0B829C4232086e
            ];
            for (uint256 id = 0; id < SECURITY_COUNCIL_MEMBERS_NUMBER; ++id) {
                if (SECURITY_COUNCIL_MEMBERS[id] == addr) {
                    // approve cut upgrade notice period if needed
                    if (!securityCouncilApproves[id]) {
                        securityCouncilApproves[id] = true;
                        numberOfApprovalsFromSecurityCouncil += 1;
                        emit ApproveCutUpgradeNoticePeriod(addr);
                        if (numberOfApprovalsFromSecurityCouncil >= SECURITY_COUNCIL_THRESHOLD) {
                            if (approvedUpgradeNoticePeriod > 0) {
                                approvedUpgradeNoticePeriod = 0;
                                emit NoticePeriodChange(approvedUpgradeNoticePeriod);
                            }
                        }
                    }
                    break;
                }
            }
        }
        /// @notice approve to decrease upgrade notice period time to zero
        /// NOTE: сan only be called after the start of the upgrade
        function cutUpgradeNoticePeriod(bytes32 targetsHash) external nonReentrant {
            require(upgradeStartTimestamp != 0, "p1");
            require(getUpgradeTargetsHash() == targetsHash, "p3"); // given targets are not in the active upgrade
            approveCutUpgradeNoticePeriod(msg.sender);
        }
        /// @notice approve to decrease upgrade notice period time to zero by signatures
        /// NOTE: Can accept many signatures at a time, thus it is possible
        /// to completely cut the upgrade notice period in one transaction
        function cutUpgradeNoticePeriodBySignature(bytes[] calldata signatures) external nonReentrant {
            require(upgradeStartTimestamp != 0, "p2");
            bytes32 targetsHash = getUpgradeTargetsHash();
            // The Message includes a hash of the addresses of the contracts to which the upgrade will take place to prevent reuse signature.
            bytes32 messageHash = keccak256(
                abi.encodePacked(
                    "\\x19Ethereum Signed Message:\
    110",
                    "Approved new ZkSync's target contracts hash\
    0x",
                    Bytes.bytesToHexASCIIBytes(abi.encodePacked(targetsHash))
                )
            );
            for (uint256 i = 0; i < signatures.length; ++i) {
                address recoveredAddress = Utils.recoverAddressFromEthSignature(signatures[i], messageHash);
                approveCutUpgradeNoticePeriod(recoveredAddress);
            }
        }
        /// @return hash of the concatenation of targets for which there is an upgrade
        /// NOTE: revert if upgrade is not active at this moment
        function getUpgradeTargetsHash() internal view returns (bytes32) {
            // Get the addresses of contracts that are being prepared for the upgrade.
            address gatekeeper = 0x38A43F4330f24fe920F943409709fc9A6084C939;
            (bool success0, bytes memory newTarget0) = gatekeeper.staticcall(
                abi.encodeWithSignature("nextTargets(uint256)", 0)
            );
            (bool success1, bytes memory newTarget1) = gatekeeper.staticcall(
                abi.encodeWithSignature("nextTargets(uint256)", 1)
            );
            (bool success2, bytes memory newTarget2) = gatekeeper.staticcall(
                abi.encodeWithSignature("nextTargets(uint256)", 2)
            );
            require(success0 && success1 && success2, "p5"); // failed to get new targets
            address newTargetAddress0 = abi.decode(newTarget0, (address));
            address newTargetAddress1 = abi.decode(newTarget1, (address));
            address newTargetAddress2 = abi.decode(newTarget2, (address));
            return keccak256(abi.encodePacked(newTargetAddress0, newTargetAddress1, newTargetAddress2));
        }
        /// @notice Set data for changing pubkey hash using onchain authorization.
        ///         Transaction author (msg.sender) should be L2 account address
        /// @notice New pubkey hash can be reset, to do that user should send two transactions:
        ///         1) First `setAuthPubkeyHash` transaction for already used `_nonce` will set timer.
        ///         2) After `AUTH_FACT_RESET_TIMELOCK` time is passed second `setAuthPubkeyHash` transaction will reset pubkey hash for `_nonce`.
        /// @param _pubkeyHash New pubkey hash
        /// @param _nonce Nonce of the change pubkey L2 transaction
        function setAuthPubkeyHash(bytes calldata _pubkeyHash, uint32 _nonce) external nonReentrant {
            requireActive();
            require(_pubkeyHash.length == PUBKEY_HASH_BYTES, "y"); // PubKeyHash should be 20 bytes.
            if (authFacts[msg.sender][_nonce] == bytes32(0)) {
                authFacts[msg.sender][_nonce] = keccak256(_pubkeyHash);
            } else {
                uint256 currentResetTimer = authFactsResetTimer[msg.sender][_nonce];
                if (currentResetTimer == 0) {
                    authFactsResetTimer[msg.sender][_nonce] = block.timestamp;
                } else {
                    require(block.timestamp.sub(currentResetTimer) >= AUTH_FACT_RESET_TIMELOCK, "z");
                    authFactsResetTimer[msg.sender][_nonce] = 0;
                    authFacts[msg.sender][_nonce] = keccak256(_pubkeyHash);
                }
            }
        }
        /// @notice Reverts unverified blocks
        function revertBlocks(StoredBlockInfo[] calldata _blocksToRevert) external nonReentrant {
            requireActive();
            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);
        }
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: UNLICENSED
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
        /**
         * @dev Moves `amount` tokens from the caller's account to `recipient`.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address recipient, uint256 amount) external;
        /**
         * @dev Returns the remaining number of tokens that `spender` will be
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
         * zero by default.
         *
         * This value changes when {approve} or {transferFrom} are called.
         */
        function allowance(address owner, address spender) external view returns (uint256);
        /**
         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
         * that someone may use both the old and the new allowance by unfortunate
         * transaction ordering. One possible solution to mitigate this race
         * condition is to first reduce the spender's allowance to 0 and set the
         * desired value afterwards:
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
         *
         * Emits an {Approval} event.
         */
        function approve(address spender, uint256 amount) external returns (bool);
        /**
         * @dev Moves `amount` tokens from `sender` to `recipient` using the
         * allowance mechanism. `amount` is then deducted from the caller's
         * allowance.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(
            address sender,
            address recipient,
            uint256 amount
        ) external;
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    import "./Config.sol";
    import "./Utils.sol";
    import "./NFTFactory.sol";
    import "./TokenGovernance.sol";
    /// @title Governance Contract
    /// @author Matter Labs
    contract Governance is Config {
        /// @notice Token added to Franklin net
        event NewToken(address indexed token, uint16 indexed tokenId);
        /// @notice Default nft factory has set
        event SetDefaultNFTFactory(address indexed factory);
        /// @notice NFT factory registered new creator account
        event NFTFactoryRegisteredCreator(
            uint32 indexed creatorAccountId,
            address indexed creatorAddress,
            address factoryAddress
        );
        /// @notice Governor changed
        event NewGovernor(address newGovernor);
        /// @notice Token Governance changed
        event NewTokenGovernance(TokenGovernance newTokenGovernance);
        /// @notice Validator's status changed
        event ValidatorStatusUpdate(address indexed validatorAddress, bool isActive);
        event TokenPausedUpdate(address indexed token, bool paused);
        /// @notice Address which will exercise governance over the network i.e. add tokens, change validator set, conduct upgrades
        address public networkGovernor;
        /// @notice Total number of ERC20 tokens registered in the network (excluding ETH, which is hardcoded as tokenId = 0)
        uint16 public totalTokens;
        /// @notice List of registered tokens by tokenId
        mapping(uint16 => address) public tokenAddresses;
        /// @notice List of registered tokens by address
        mapping(address => uint16) public tokenIds;
        /// @notice List of permitted validators
        mapping(address => bool) public validators;
        /// @notice Paused tokens list, deposits are impossible to create for paused tokens
        mapping(uint16 => bool) public pausedTokens;
        /// @notice Address that is authorized to add tokens to the Governance.
        TokenGovernance public tokenGovernance;
        /// @notice NFT Creator address to factory address mapping
        mapping(uint32 => mapping(address => NFTFactory)) public nftFactories;
        /// @notice Address which will be used if NFT token has no factories
        NFTFactory public defaultFactory;
        /// @notice Governance contract initialization. Can be external because Proxy contract intercepts illegal calls of this function.
        /// @param initializationParameters Encoded representation of initialization parameters:
        ///     _networkGovernor The address of network governor
        function initialize(bytes calldata initializationParameters) external {
            address _networkGovernor = abi.decode(initializationParameters, (address));
            networkGovernor = _networkGovernor;
        }
        /// @notice Governance contract upgrade. Can be external because Proxy contract intercepts illegal calls of this function.
        /// @param upgradeParameters Encoded representation of upgrade parameters
        // solhint-disable-next-line no-empty-blocks
        function upgrade(bytes calldata upgradeParameters) external {}
        /// @notice Change current governor
        /// @param _newGovernor Address of the new governor
        function changeGovernor(address _newGovernor) external {
            require(_newGovernor != address(0), "1n");
            requireGovernor(msg.sender);
            if (networkGovernor != _newGovernor) {
                networkGovernor = _newGovernor;
                emit NewGovernor(_newGovernor);
            }
        }
        /// @notice Change current token governance
        /// @param _newTokenGovernance Address of the new token governor
        function changeTokenGovernance(TokenGovernance _newTokenGovernance) external {
            requireGovernor(msg.sender);
            if (tokenGovernance != _newTokenGovernance) {
                tokenGovernance = _newTokenGovernance;
                emit NewTokenGovernance(_newTokenGovernance);
            }
        }
        /// @notice Add token to the list of networks tokens
        /// @param _token Token address
        function addToken(address _token) external {
            require(msg.sender == address(tokenGovernance), "1E");
            require(tokenIds[_token] == 0, "1e"); // token exists
            require(totalTokens < MAX_AMOUNT_OF_REGISTERED_TOKENS, "1f"); // no free identifiers for tokens
            totalTokens++;
            uint16 newTokenId = totalTokens; // it is not `totalTokens - 1` because tokenId = 0 is reserved for eth
            tokenAddresses[newTokenId] = _token;
            tokenIds[_token] = newTokenId;
            emit NewToken(_token, newTokenId);
        }
        /// @notice Pause token deposits for the given token
        /// @param _tokenAddr Token address
        /// @param _tokenPaused Token paused status
        function setTokenPaused(address _tokenAddr, bool _tokenPaused) external {
            requireGovernor(msg.sender);
            uint16 tokenId = this.validateTokenAddress(_tokenAddr);
            if (pausedTokens[tokenId] != _tokenPaused) {
                pausedTokens[tokenId] = _tokenPaused;
                emit TokenPausedUpdate(_tokenAddr, _tokenPaused);
            }
        }
        /// @notice Change validator status (active or not active)
        /// @param _validator Validator address
        /// @param _active Active flag
        function setValidator(address _validator, bool _active) external {
            requireGovernor(msg.sender);
            if (validators[_validator] != _active) {
                validators[_validator] = _active;
                emit ValidatorStatusUpdate(_validator, _active);
            }
        }
        /// @notice Check if specified address is is governor
        /// @param _address Address to check
        function requireGovernor(address _address) public view {
            require(_address == networkGovernor, "1g"); // only by governor
        }
        /// @notice Checks if validator is active
        /// @param _address Validator address
        function requireActiveValidator(address _address) external view {
            require(validators[_address], "1h"); // validator is not active
        }
        /// @notice Validate token id (must be less than or equal to total tokens amount)
        /// @param _tokenId Token id
        /// @return bool flag that indicates if token id is less than or equal to total tokens amount
        function isValidTokenId(uint16 _tokenId) external view returns (bool) {
            return _tokenId <= totalTokens;
        }
        /// @notice Validate token address
        /// @param _tokenAddr Token address
        /// @return tokens id
        function validateTokenAddress(address _tokenAddr) external view returns (uint16) {
            uint16 tokenId = tokenIds[_tokenAddr];
            require(tokenId != 0, "1i"); // 0 is not a valid token
            return tokenId;
        }
        function packRegisterNFTFactoryMsg(
            uint32 _creatorAccountId,
            address _creatorAddress,
            address _factoryAddress
        ) internal pure returns (bytes memory) {
            return
                abi.encodePacked(
                    "\\x19Ethereum Signed Message:\
    141",
                    "\
    Creator's account ID in zkSync: ",
                    Bytes.bytesToHexASCIIBytes(abi.encodePacked((_creatorAccountId))),
                    "\
    Creator: ",
                    Bytes.bytesToHexASCIIBytes(abi.encodePacked((_creatorAddress))),
                    "\
    Factory: ",
                    Bytes.bytesToHexASCIIBytes(abi.encodePacked((_factoryAddress)))
                );
        }
        /// @notice Register creator corresponding to the factory
        /// @param _creatorAccountId Creator's zkSync account ID
        /// @param _creatorAddress NFT creator address
        /// @param _signature Creator's signature
        function registerNFTFactoryCreator(
            uint32 _creatorAccountId,
            address _creatorAddress,
            bytes memory _signature
        ) external {
            require(address(nftFactories[_creatorAccountId][_creatorAddress]) == address(0), "Q");
            bytes32 messageHash = keccak256(packRegisterNFTFactoryMsg(_creatorAccountId, _creatorAddress, msg.sender));
            address recoveredAddress = Utils.recoverAddressFromEthSignature(_signature, messageHash);
            require(recoveredAddress == _creatorAddress, "ws");
            nftFactories[_creatorAccountId][_creatorAddress] = NFTFactory(msg.sender);
            emit NFTFactoryRegisteredCreator(_creatorAccountId, _creatorAddress, msg.sender);
        }
        /// @notice Set default factory for our contract. This factory will be used to mint an NFT token that has no factory
        /// @param _factory Address of NFT factory
        function setDefaultNFTFactory(address _factory) external {
            requireGovernor(msg.sender);
            require(address(_factory) != address(0), "mb1"); // Factory should be non zero
            require(address(defaultFactory) == address(0), "mb2"); // NFTFactory is already set
            defaultFactory = NFTFactory(_factory);
            emit SetDefaultNFTFactory(_factory);
        }
        function getNFTFactory(uint32 _creatorAccountId, address _creatorAddress) external view returns (NFTFactory) {
            NFTFactory _factory = nftFactories[_creatorAccountId][_creatorAddress];
            // even if the factory is undefined or has been destroyed, the user can mint NFT
            if (address(_factory) == address(0) || !isContract(address(_factory))) {
                require(address(defaultFactory) != address(0), "fs"); // NFTFactory does not set
                return defaultFactory;
            } else {
                return _factory;
            }
        }
        /// @return whether the address is a contract or not
        /// NOTE: for smart contracts that called `selfdestruct` will return a negative result
        function isContract(address _address) internal view returns (bool) {
            uint256 contractSize;
            assembly {
                contractSize := extcodesize(_address)
            }
            return contractSize != 0;
        }
    }
    pragma solidity ^0.7.0;
    pragma experimental ABIEncoderV2;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    import "./KeysWithPlonkVerifier.sol";
    import "./Config.sol";
    // Hardcoded constants to avoid accessing store
    contract Verifier is KeysWithPlonkVerifier, KeysWithPlonkVerifierOld, Config {
        // solhint-disable-next-line no-empty-blocks
        function initialize(bytes calldata) external {}
        /// @notice Verifier contract upgrade. Can be external because Proxy contract intercepts illegal calls of this function.
        /// @param upgradeParameters Encoded representation of upgrade parameters
        // solhint-disable-next-line no-empty-blocks
        function upgrade(bytes calldata upgradeParameters) external {}
        function verifyAggregatedBlockProof(
            uint256[] memory _recursiveInput,
            uint256[] calldata _proof,
            uint8[] memory _vkIndexes,
            uint256[] memory _individualVksInputs,
            uint256[16] memory _subproofsLimbs
        ) external view returns (bool) {
            for (uint256 i = 0; i < _individualVksInputs.length; ++i) {
                uint256 commitment = _individualVksInputs[i];
                _individualVksInputs[i] = commitment & INPUT_MASK;
            }
            VerificationKey memory vk = getVkAggregated(uint32(_vkIndexes.length));
            return
                verify_serialized_proof_with_recursion(
                    _recursiveInput,
                    _proof,
                    VK_TREE_ROOT,
                    VK_MAX_INDEX,
                    _vkIndexes,
                    _individualVksInputs,
                    _subproofsLimbs,
                    vk
                );
        }
        function verifyExitProof(
            bytes32 _rootHash,
            uint32 _accountId,
            address _owner,
            uint32 _tokenId,
            uint128 _amount,
            uint32 _nftCreatorAccountId,
            address _nftCreatorAddress,
            uint32 _nftSerialId,
            bytes32 _nftContentHash,
            uint256[] calldata _proof
        ) external view returns (bool) {
            bytes32 commitment = sha256(
                abi.encodePacked(
                    _rootHash,
                    _accountId,
                    _owner,
                    _tokenId,
                    _amount,
                    _nftCreatorAccountId,
                    _nftCreatorAddress,
                    _nftSerialId,
                    _nftContentHash
                )
            );
            uint256[] memory inputs = new uint256[](1);
            inputs[0] = uint256(commitment) & INPUT_MASK;
            ProofOld memory proof = deserialize_proof_old(inputs, _proof);
            VerificationKeyOld memory vk = getVkExit();
            require(vk.num_inputs == inputs.length, "n1");
            return verify_old(proof, vk);
        }
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: UNLICENSED
    interface NFTFactory {
        function mintNFTFromZkSync(
            address creator,
            address recipient,
            uint32 creatorAccountId,
            uint32 serialId,
            bytes32 contentHash,
            // Even though the token id can fit into the uint32, we still use
            // the uint256 to preserve consistency with the ERC721 parent contract
            uint256 tokenId
        ) external;
        event MintNFTFromZkSync(
            address indexed creator,
            address indexed recipient,
            uint32 creatorAccountId,
            uint32 serialId,
            bytes32 contentHash,
            uint256 tokenId
        );
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    import "./ReentrancyGuard.sol";
    import "./Governance.sol";
    import "./ITrustedTransfarableERC20.sol";
    import "./Utils.sol";
    /// @title Token Governance Contract
    /// @author Matter Labs
    /// @notice Contract is used to allow anyone to add new ERC20 tokens to zkSync given sufficient payment
    contract TokenGovernance is ReentrancyGuard {
        /// @notice Token lister added or removed (see `tokenLister`)
        event TokenListerUpdate(address indexed tokenLister, bool isActive);
        /// @notice Listing fee token set
        event ListingFeeTokenUpdate(ITrustedTransfarableERC20 indexed newListingFeeToken, uint256 newListingFee);
        /// @notice Listing fee set
        event ListingFeeUpdate(uint256 newListingFee);
        /// @notice Maximum number of listed tokens updated
        event ListingCapUpdate(uint16 newListingCap);
        /// @notice The treasury (the account which will receive the fee) was updated
        event TreasuryUpdate(address newTreasury);
        /// @notice zkSync governance contract
        Governance public governance;
        /// @notice Token used to collect listing fee for addition of new token to zkSync network
        ITrustedTransfarableERC20 public listingFeeToken;
        /// @notice Token listing fee
        uint256 public listingFee;
        /// @notice Max number of tokens that can be listed using this contract
        uint16 public listingCap;
        /// @notice Addresses that can list tokens without fee
        mapping(address => bool) public tokenLister;
        /// @notice Address that collects listing payments
        address public treasury;
        constructor(
            Governance _governance,
            ITrustedTransfarableERC20 _listingFeeToken,
            uint256 _listingFee,
            uint16 _listingCap,
            address _treasury
        ) {
            initializeReentrancyGuard();
            governance = _governance;
            listingFeeToken = _listingFeeToken;
            listingFee = _listingFee;
            listingCap = _listingCap;
            treasury = _treasury;
            address governor = governance.networkGovernor();
            // We add zkSync governor as a first token lister.
            tokenLister[governor] = true;
            emit TokenListerUpdate(governor, true);
        }
        /// @notice Adds new ERC20 token to zkSync network.
        /// @notice If caller is not present in the `tokenLister` map payment of `listingFee` in `listingFeeToken` should be made.
        /// @notice NOTE: before calling this function make sure to approve `listingFeeToken` transfer for this contract.
        function addToken(address _token) external nonReentrant {
            require(_token != address(0), "z1"); // Token should have a non-zero address
            require(_token != 0xaBEA9132b05A70803a4E85094fD0e1800777fBEF, "z2"); // Address of the token cannot be the same as the address of the main zksync contract
            require(governance.totalTokens() < listingCap, "can't add more tokens"); // Impossible to add more tokens using this contract
            if (!tokenLister[msg.sender] && listingFee > 0) {
                // Collect fees
                bool feeTransferOk = listingFeeToken.transferFrom(msg.sender, treasury, listingFee);
                require(feeTransferOk, "fee transfer failed"); // Failed to receive payment for token addition.
            }
            governance.addToken(_token);
        }
        /// Governance functions (this contract is governed by zkSync governor)
        /// @notice Set new listing token and fee
        /// @notice Can be called only by zkSync governor
        function setListingFeeToken(ITrustedTransfarableERC20 _newListingFeeToken, uint256 _newListingFee) external {
            governance.requireGovernor(msg.sender);
            listingFeeToken = _newListingFeeToken;
            listingFee = _newListingFee;
            emit ListingFeeTokenUpdate(_newListingFeeToken, _newListingFee);
        }
        /// @notice Set new listing fee
        /// @notice Can be called only by zkSync governor
        function setListingFee(uint256 _newListingFee) external {
            governance.requireGovernor(msg.sender);
            listingFee = _newListingFee;
            emit ListingFeeUpdate(_newListingFee);
        }
        /// @notice Enable or disable token lister. If enabled new tokens can be added by that address without payment
        /// @notice Can be called only by zkSync governor
        function setLister(address _listerAddress, bool _active) external {
            governance.requireGovernor(msg.sender);
            if (tokenLister[_listerAddress] != _active) {
                tokenLister[_listerAddress] = _active;
                emit TokenListerUpdate(_listerAddress, _active);
            }
        }
        /// @notice Change maximum amount of tokens that can be listed using this method
        /// @notice Can be called only by zkSync governor
        function setListingCap(uint16 _newListingCap) external {
            governance.requireGovernor(msg.sender);
            listingCap = _newListingCap;
            emit ListingCapUpdate(_newListingCap);
        }
        /// @notice Change address that collects payments for listing tokens.
        /// @notice Can be called only by zkSync governor
        function setTreasury(address _newTreasury) external {
            governance.requireGovernor(msg.sender);
            treasury = _newTreasury;
            emit TreasuryUpdate(_newTreasury);
        }
    }
    
    /// @dev Interface of the ERC20 standard as defined in the EIP.
    /// 1. Implements only `transfer` and `transferFrom` methods
    /// 2. These methods return a boolean value in case of a non-revert call
    /// NOTE: It is expected that if the function returns true, then the user's balance has
    /// changed exactly by `amount` according to the ERC20 standard.
    /// Note: Used to perform transfers for tokens that explicitly return a boolean value
    /// (if the token returns any other data or does not return at all, then the function call will be reverted)
    interface ITrustedTransfarableERC20 {
        /**
         * @dev Moves `amount` tokens from the caller's account to `recipient`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address recipient, uint256 amount) external returns (bool);
        /**
         * @dev Moves `amount` tokens from `sender` to `recipient` using the
         * allowance mechanism. `amount` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(
            address sender,
            address recipient,
            uint256 amount
        ) external returns (bool);
    }
    pragma solidity ^0.7.0;
    pragma experimental ABIEncoderV2;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    import "./PlonkCore.sol";
    // Hardcoded constants to avoid accessing store
    contract KeysWithPlonkVerifier is VerifierWithDeserialize {
        uint256 constant VK_TREE_ROOT = 0x1ffdc7ebe04681d451ae0658a6ad27feb63835b0edf90bdfa203cd8d12282ace;
        uint8 constant VK_MAX_INDEX = 3;
        function getVkAggregated(uint32 _proofs) internal pure returns (VerificationKey memory vk) {
            if (_proofs == uint32(1)) { return getVkAggregated1(); }
            else if (_proofs == uint32(4)) { return getVkAggregated4(); }
            else if (_proofs == uint32(8)) { return getVkAggregated8(); }
        }
        
        function getVkAggregated1() internal pure returns(VerificationKey memory vk) {
            vk.domain_size = 4194304;
            vk.num_inputs = 1;
            vk.omega = PairingsBn254.new_fr(0x18c95f1ae6514e11a1b30fd7923947c5ffcec5347f16e91b4dd654168326bede);
            vk.gate_setup_commitments[0] = PairingsBn254.new_g1(
                0x16782f42f191b0b1841c2b6a42b7f0564af065d04818526df6c3ad41fe35f8da,
                0x125b9c68c0b931578f8a18fd23ce08e7b7c082ad76404ccece796fa9b3ec0cb0
            );
            vk.gate_setup_commitments[1] = PairingsBn254.new_g1(
                0x2511833eee308a3936b23b27c929942a60aa780747bf32143dc183e873144bfd,
                0x1b8d88d78fcc4a36ebe90fbbdc4547442411e0c8d484727d5c7c6eec27ad2df0
            );
            vk.gate_setup_commitments[2] = PairingsBn254.new_g1(
                0x2945641d0c5556aa333ef6c8431e24379b73eccbed7ff3e9425cc64aee1e92ed,
                0x25bbf079192cc83f160da9375e7aec3d3d2caac8d831a29b50f5497071fc14c6
            );
            vk.gate_setup_commitments[3] = PairingsBn254.new_g1(
                0x09b3c361e5895a8e074eb9b9a9e57af59966f0464068460adc3f64e58544afa4,
                0x0412a017f775dd05af16cf387a1e822c2a7e0f8b7cfabd0eb4eb0f67b20e4ada
            );
            vk.gate_setup_commitments[4] = PairingsBn254.new_g1(
                0x244b30447ab3e56bb5a5a7f0ef8463a4047476ea269735a887b3de568b3401a3,
                0x2ba860198d5e6e0fd93355cb5f309e7e4c1113a57222830961999b79b83d700f
            );
            vk.gate_setup_commitments[5] = PairingsBn254.new_g1(
                0x0e13af99775bf5555c366e9c8d4af25a2e195807b766b422856525c01a38b12d,
                0x1787389894222dba5371ab55d512460c5205c1baa0421fc877b183025079a472
            );
            vk.gate_setup_commitments[6] = PairingsBn254.new_g1(
                0x233a03f89c094cf39c89020772d9b912bd0c303d211002ee5afc5c59e241f02b,
                0x04fa51fca1b17399bbbf2b99f17bbce6af1f50b085add4c41ac4ea64f65f4674
            );
            vk.gate_selector_commitments[0] = PairingsBn254.new_g1(
                0x1ca088ed531e65b722c8b48568359bbe11051b86f1a8e8951eacc615d9faed3b,
                0x074b06c09de93dd79e070a9ded635e21a34d7178e9a670766e8208149c28e339
            );
            vk.gate_selector_commitments[1] = PairingsBn254.new_g1(
                0x2b4c77c0d47676559061b47968a044aec625cb907181457428e5d08df9b27ef8,
                0x1c1be561bdc3eba16162886a2943882157f98ed8246f2063028497f1c108fa93
            );
            vk.copy_permutation_commitments[0] = PairingsBn254.new_g1(
                0x238fd7f2cbc3c3e5899483633c78f051e6d6d25f31aaa6b32b863d55b20d641a,
                0x1f9877b625eaae7a084582a2ffce326a6a5558f3efdb3367037098c4ca25a647
            );
            vk.copy_permutation_commitments[1] = PairingsBn254.new_g1(
                0x0b126f60653e371f3f2a85301f16e9cf4af04922a2725fc131b17e90e13d0d84,
                0x13bc3f0c7475b74591827463943b35cfd05adb7094a79eeeee2067e8e28a8e84
            );
            vk.copy_permutation_commitments[2] = PairingsBn254.new_g1(
                0x06cae3c1e5b43afb4dda3243c99da693a27eba065fd61a873e99e2c85fd22719,
                0x14343c6bdcc85b01b053f26aa3c473cb2f24747ba6d6b90b2323b24f3dfd127e
            );
            vk.copy_permutation_commitments[3] = PairingsBn254.new_g1(
                0x217564e2c710d050161b57ef2700e1676251a6d457c4b0d94c41a4492d6dcea3,
                0x2365779642d63803d0265a7cc666b3af6ad92b7e9ef38d9113db1208b83f0732
            );
            vk.copy_permutation_non_residues[0] = PairingsBn254.new_fr(
                0x0000000000000000000000000000000000000000000000000000000000000005
            );
            vk.copy_permutation_non_residues[1] = PairingsBn254.new_fr(
                0x0000000000000000000000000000000000000000000000000000000000000007
            );
            vk.copy_permutation_non_residues[2] = PairingsBn254.new_fr(
                0x000000000000000000000000000000000000000000000000000000000000000a
            );
            vk.g2_x = PairingsBn254.new_g2(
                [0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1,
                0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0],
                [0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4,
                0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55]
            );
        }
        
        function getVkAggregated4() internal pure returns(VerificationKey memory vk) {
            vk.domain_size = 8388608;
            vk.num_inputs = 1;
            vk.omega = PairingsBn254.new_fr(0x1283ba6f4b7b1a76ba2008fe823128bea4adb9269cbfd7c41c223be65bc60863);
            vk.gate_setup_commitments[0] = PairingsBn254.new_g1(
                0x1878d6c837a0f16cb055d3a4e79fba0d85de670dacd708dadd55407b0619796d,
                0x0b3282e52a38ecec63ba42710e8d1ad5c8715c7ed07ce217a3eec747a3f37d76
            );
            vk.gate_setup_commitments[1] = PairingsBn254.new_g1(
                0x07425bcaf480e377886678d5b5432f0945e3fc952126503a7b672dc4b03f2c26,
                0x155b8003ea27945bf43fb5f43291f76e2aa361e0ec81550c0af66dcd1dc8077e
            );
            vk.gate_setup_commitments[2] = PairingsBn254.new_g1(
                0x1292b8795f05fc50782ea7303e2b65a7b2f0e1cc3dead51dfa0b9d2183e5d907,
                0x220d344a384ac53f682e1be6c69407a1fadd0a589de36b95ddc4da05693ba679
            );
            vk.gate_setup_commitments[3] = PairingsBn254.new_g1(
                0x283412c1942c0cb3fffc935aab313a37510888bd5ae5972d8d67edc2312af895,
                0x1040e655967354e7ae9227c6200c2256cdcbb707e7158b66462aba23d96b8de2
            );
            vk.gate_setup_commitments[4] = PairingsBn254.new_g1(
                0x2abe282377038904420434202c11a4f849e64babd436b93192d8d9c34d28ce44,
                0x19f0ed010326da1cf8ac93a0f73617ab7c9acb30a0c23a26db9ec19ab6a52fcb
            );
            vk.gate_setup_commitments[5] = PairingsBn254.new_g1(
                0x236f01e67b19be0e7487100a14fd04a05a83a5660966ace987c5248f8c883459,
                0x0ebe824fb1e778491bcb8091d2adbc18dceda4fa9ee191b71c5834a71c533c41
            );
            vk.gate_setup_commitments[6] = PairingsBn254.new_g1(
                0x2ad3c37aa0b1335f6c70d0e10f0a123a28ea012e857df30e3ced524ef6562c71,
                0x1b52d7ac4ee6082438deab8ab0f2944c9fd53258de305065f8323a3767dd8234
            );
            vk.gate_selector_commitments[0] = PairingsBn254.new_g1(
                0x173c39587688a8967e915959df613aecf44ad0c7d2019ec32311bccdf542c78e,
                0x2421a36a67559ed89afbff081cd45b318835e2b0233c047d030abc48b5011c22
            );
            vk.gate_selector_commitments[1] = PairingsBn254.new_g1(
                0x177d8ef11cac24105d4b38e035b891986d163d9df717fce12d18af324f86d2dc,
                0x02cd01ba1c82c85b4f0f8c7304254de64516857ac4f7bb60f052bb2af98132c5
            );
            vk.copy_permutation_commitments[0] = PairingsBn254.new_g1(
                0x21da2c0f2b7849d4c44dbc487d370cccbae78fbd979e79575e04b7a983f2f68a,
                0x14ffb806769ccf0d2c692cd93653491966525554d79efc37cfba5a5c08b15039
            );
            vk.copy_permutation_commitments[1] = PairingsBn254.new_g1(
                0x184cc2f37e687a9be2404cd367536f14a505f086fd597cb966c5b753f325adb4,
                0x20aaed49755efed4814025ac679570f62b8c98a1b8d977969242c3ffa67884d6
            );
            vk.copy_permutation_commitments[2] = PairingsBn254.new_g1(
                0x0a2dee920031d9cd5ed499dc3cb901657079f6a2dfb0ba389b0181803bb91e24,
                0x272ac2a214f46be0ed7d2b4cf125504ef82d929b1c1ec0a81655c66f39403cd1
            );
            vk.copy_permutation_commitments[3] = PairingsBn254.new_g1(
                0x07e360365c7a5363389b2d2449b9471754591f01a623fd5553c5cfe6bad19aaf,
                0x1b814914958835ef86de3c26c6c4bdc27e947f38cb0d2bfaa421d66cabfb7d55
            );
            vk.copy_permutation_non_residues[0] = PairingsBn254.new_fr(
                0x0000000000000000000000000000000000000000000000000000000000000005
            );
            vk.copy_permutation_non_residues[1] = PairingsBn254.new_fr(
                0x0000000000000000000000000000000000000000000000000000000000000007
            );
            vk.copy_permutation_non_residues[2] = PairingsBn254.new_fr(
                0x000000000000000000000000000000000000000000000000000000000000000a
            );
            vk.g2_x = PairingsBn254.new_g2(
                [0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1,
                0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0],
                [0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4,
                0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55]
            );
        }
        
        function getVkAggregated8() internal pure returns(VerificationKey memory vk) {
            vk.domain_size = 16777216;
            vk.num_inputs = 1;
            vk.omega = PairingsBn254.new_fr(0x1951441010b2b95a6e47a6075066a50a036f5ba978c050f2821df86636c0facb);
            vk.gate_setup_commitments[0] = PairingsBn254.new_g1(
                0x1aab46b9aa3adcac623c360e4d075572e3f56f4c75ac3b8663a7b059bd9b1857,
                0x166ac39283efa3d6cb36423e83e2360f006e5fa374b454dea5fe92cc50d4193f
            );
            vk.gate_setup_commitments[1] = PairingsBn254.new_g1(
                0x13bce0a7bfbf2e7a81f18e84966c32422446b01f54cc7dc2ad3f64e92fe94cad,
                0x0247234b0cdfd8c95a767f84303c3dd65ce7b15856c2840635d9d4754ba99479
            );
            vk.gate_setup_commitments[2] = PairingsBn254.new_g1(
                0x08742bad9a7cbcc9dbb49a25bebce179295d1cf70fd8f9c8e82b8a658ee0b67c,
                0x2a467983257850c5fa27f2f52f0c5c5fc98e7d2e0d440a8fd954ad981ff0ce9f
            );
            vk.gate_setup_commitments[3] = PairingsBn254.new_g1(
                0x16ebdd4b95b872cd09c13b6b54a8b8bf81a01529a71234db26e3b22c6d632723,
                0x034219d7ad9ef204cfb3e32c4a47af82eea40504c2b1bac785104731722ed617
            );
            vk.gate_setup_commitments[4] = PairingsBn254.new_g1(
                0x2e3a7c4458a8dc1535e68bac5dd5c1c9ff3886df4156bad4a08fcd08ebf1db26,
                0x173859705317db06e5b7d260898ab08e72fae987c272b82345105d72bfd00ab8
            );
            vk.gate_setup_commitments[5] = PairingsBn254.new_g1(
                0x0b830132e3325eaaea73c1095e615358db38dfb39248c90f8ff4afde169e7657,
                0x0bfedf8cfce7260c16bb1f76ad9a39f73a68087e5c68e841020aeaa5ba301a9f
            );
            vk.gate_setup_commitments[6] = PairingsBn254.new_g1(
                0x1660c850da793add523f7990b983896e50d5549eec7990ec26aabc220ca58d52,
                0x0ba698e78dee0d41cf8aefde82c5bfda38be071e11025b56db779ddb40a4fe92
            );
            vk.gate_selector_commitments[0] = PairingsBn254.new_g1(
                0x024fe4ce02dd48937e4642b66308ae15d731e0ea82fc5430a0470d9a5dab3694,
                0x177cac2d79a8bfa6aba134e24bded06d06219979c18b2fa4fe71baea9885985d
            );
            vk.gate_selector_commitments[1] = PairingsBn254.new_g1(
                0x00a848bc76c52faf7d4e7cc4086b50e3ccc9b1cebef130ac1bbf1816502df59d,
                0x02f42f326f82b33cb9e4e7cfb332889eec95c2813f7968b3a50d838b3cbfa676
            );
            vk.copy_permutation_commitments[0] = PairingsBn254.new_g1(
                0x20c176738979e0d1ea9541bf26e6209d3091b618ae94f3c72e13e954a1614f60,
                0x2a7019c81009c00a7412b6a303b2eb118a362a558837e9ecdb912589bc11ff83
            );
            vk.copy_permutation_commitments[1] = PairingsBn254.new_g1(
                0x10a92b3fa2b8280030c9de5cbcab4da3cf9b5b3f63f3ad60284ecded63cc54ea,
                0x1bde2a83db435b8c74e4239b4f8416da88008331a758d8c68a9104f2dfc3e237
            );
            vk.copy_permutation_commitments[2] = PairingsBn254.new_g1(
                0x08e2e513d1e548a627e2d4f74d28dea916d8598415b70543bb3e92429f0111cb,
                0x2fb46898f77e32d7fd646fe31b60320423aa4698501e329e206b6acfcfb01337
            );
            vk.copy_permutation_commitments[3] = PairingsBn254.new_g1(
                0x145b88d324270872b13784fbb7ccdee6e5593d2d5cbc81f4aaa9b4268cfc5094,
                0x197d826aaf2a9853ca98ec9c0e55376eec1a6a0f5dbbbe02afeb1b567d8eafa0
            );
            vk.copy_permutation_non_residues[0] = PairingsBn254.new_fr(
                0x0000000000000000000000000000000000000000000000000000000000000005
            );
            vk.copy_permutation_non_residues[1] = PairingsBn254.new_fr(
                0x0000000000000000000000000000000000000000000000000000000000000007
            );
            vk.copy_permutation_non_residues[2] = PairingsBn254.new_fr(
                0x000000000000000000000000000000000000000000000000000000000000000a
            );
            vk.g2_x = PairingsBn254.new_g2(
                [0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1,
                0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0],
                [0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4,
                0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55]
            );
        }
        
    }
    // Hardcoded constants to avoid accessing store
    contract KeysWithPlonkVerifierOld is VerifierWithDeserializeOld {
        
        function getVkExit() internal pure returns(VerificationKeyOld memory vk) {
            vk.domain_size = 524288;
            vk.num_inputs = 1;
            vk.omega = PairingsBn254.new_fr(0x0cf1526aaafac6bacbb67d11a4077806b123f767e4b0883d14cc0193568fc082);
            vk.selector_commitments[0] = PairingsBn254.new_g1(
                0x114dd473f77a15b602201577dd4b64a32a783cb32fbc02911e512df6a219695d,
                0x04c68f82a5dd7d0cc90318bdff493b3d552d148ad859c373ffe55275e043c43b
            );
            vk.selector_commitments[1] = PairingsBn254.new_g1(
                0x245e8c882af503cb5421f5135b4295a920ccf68b42ae7fb967f044f54e2aaa29,
                0x071322ee387a9ce49fe7ef2edb6e9237203dee49ec47483af85e356b79fb06fd
            );
            vk.selector_commitments[2] = PairingsBn254.new_g1(
                0x0187754ab593b07a420b3b4d215c20ed49acf90fc4c97e4b06e8f5bc0a2eb3f4,
                0x0170f9286ce950286a16ea25136c163c0b32019f31b89c256a612d40b863d0b6
            );
            vk.selector_commitments[3] = PairingsBn254.new_g1(
                0x0defecfae1d2b9ec9b2ee4d4798c625fa50f6a4ddb7747a7293df0c17fcb90c2,
                0x0f91d08fceebf85fb80f12cda78cefa1ee9dbf5cfe7c4f0704b3c6620fa50c55
            );
            vk.selector_commitments[4] = PairingsBn254.new_g1(
                0x2f7fef3b3fb64af6640f93803a18b3e5ce4e0e60aecd4f924c833fa6fa6da961,
                0x03908fc737113ac7f3529fe3b36efca200c66d1d85d2fc081973214c586de732
            );
            vk.selector_commitments[5] = PairingsBn254.new_g1(
                0x14ce3c0e9b78fc331327249e707f58fa4bb0ed746bdc9c2262ad0cf905609627,
                0x09e64fdac452b424e98fc4a92f7222693d0d84ab48aadd9c46151dbe5f1a34a9
            );
            // we only have access to value of the d(x) witness polynomial on the next
            // trace step, so we only need one element here and deal with it in other places
            // by having this in mind
            vk.next_step_selector_commitments[0] = PairingsBn254.new_g1(
                0x1d10bfd923c17d9623ec02db00099355b373021432ae1edef69b0f5f461f78d6,
                0x24e370a93f65f42888781d0158bb6ef9136c8bbd047d7993b8276bc8df8b640a
            );
            vk.permutation_commitments[0] = PairingsBn254.new_g1(
                0x1fd1755ed4d06d91d50db4771d332cfa2bc2ca0e10ac8b77e0d6b73b993e788e,
                0x0bdbf3b7f0d3cffdcf818f1fba18b90914eda59b454bd1858c6c0916b817f883
            );
            vk.permutation_commitments[1] = PairingsBn254.new_g1(
                0x1f3b8d12ffa2ceb2bb42d232ad2cf11bce3183472b622e11cc841d26f42ad507,
                0x0ce815e32b3bd14311cde210cda1bd351617d539ed3e9d96a8605f364f3a29b0
            );
            vk.permutation_commitments[2] = PairingsBn254.new_g1(
                0x123afa8c1cec1956d7330db062498a2a3e3a9862926c02e1228d9cfb63d3c301,
                0x0f5af15ff0a3e35486c541f72956b53ff6d0740384ef6463c866146c1bd2afc8
            );
            vk.permutation_commitments[3] = PairingsBn254.new_g1(
                0x01069e38ea6396af1623921101d3d3d14ee46942fb23bf1d110efb994c3ee573,
                0x232a8ce7151e69601a7867f9dcac8e2de4dd8352d119c90bbb0fb84720c02513
            );
            vk.permutation_non_residues[0] = PairingsBn254.new_fr(
                0x0000000000000000000000000000000000000000000000000000000000000005
            );
            vk.permutation_non_residues[1] = PairingsBn254.new_fr(
                0x0000000000000000000000000000000000000000000000000000000000000007
            );
            vk.permutation_non_residues[2] = PairingsBn254.new_fr(
                0x000000000000000000000000000000000000000000000000000000000000000a
            );
            vk.g2_x = PairingsBn254.new_g2(
                [0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1, 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0],
                [0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4, 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55]
            );
        }
        
    }
    pragma solidity >=0.5.0 <0.8.0;
    pragma experimental ABIEncoderV2;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    // solhint-disable
    library PairingsBn254 {
        uint256 constant q_mod = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
        uint256 constant r_mod = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
        uint256 constant bn254_b_coeff = 3;
        struct G1Point {
            uint256 X;
            uint256 Y;
        }
        struct Fr {
            uint256 value;
        }
        function new_fr(uint256 fr) internal pure returns (Fr memory) {
            require(fr < r_mod);
            return Fr({value: fr});
        }
        function copy(Fr memory self) internal pure returns (Fr memory n) {
            n.value = self.value;
        }
        function assign(Fr memory self, Fr memory other) internal pure {
            self.value = other.value;
        }
        function inverse(Fr memory fr) internal view returns (Fr memory) {
            require(fr.value != 0);
            return pow(fr, r_mod - 2);
        }
        function add_assign(Fr memory self, Fr memory other) internal pure {
            self.value = addmod(self.value, other.value, r_mod);
        }
        function sub_assign(Fr memory self, Fr memory other) internal pure {
            self.value = addmod(self.value, r_mod - other.value, r_mod);
        }
        function mul_assign(Fr memory self, Fr memory other) internal pure {
            self.value = mulmod(self.value, other.value, r_mod);
        }
        function pow(Fr memory self, uint256 power) internal view returns (Fr memory) {
            uint256[6] memory input = [32, 32, 32, self.value, power, r_mod];
            uint256[1] memory result;
            bool success;
            assembly {
                success := staticcall(gas(), 0x05, input, 0xc0, result, 0x20)
            }
            require(success);
            return Fr({value: result[0]});
        }
        // Encoding of field elements is: X[0] * z + X[1]
        struct G2Point {
            uint256[2] X;
            uint256[2] Y;
        }
        function P1() internal pure returns (G1Point memory) {
            return G1Point(1, 2);
        }
        function new_g1(uint256 x, uint256 y) internal pure returns (G1Point memory) {
            return G1Point(x, y);
        }
        function new_g1_checked(uint256 x, uint256 y) internal pure returns (G1Point memory) {
            if (x == 0 && y == 0) {
                // point of infinity is (0,0)
                return G1Point(x, y);
            }
            // check encoding
            require(x < q_mod);
            require(y < q_mod);
            // check on curve
            uint256 lhs = mulmod(y, y, q_mod); // y^2
            uint256 rhs = mulmod(x, x, q_mod); // x^2
            rhs = mulmod(rhs, x, q_mod); // x^3
            rhs = addmod(rhs, bn254_b_coeff, q_mod); // x^3 + b
            require(lhs == rhs);
            return G1Point(x, y);
        }
        function new_g2(uint256[2] memory x, uint256[2] memory y) internal pure returns (G2Point memory) {
            return G2Point(x, y);
        }
        function copy_g1(G1Point memory self) internal pure returns (G1Point memory result) {
            result.X = self.X;
            result.Y = self.Y;
        }
        function P2() internal pure returns (G2Point memory) {
            // for some reason ethereum expects to have c1*v + c0 form
            return
                G2Point(
                    [
                        0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2,
                        0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed
                    ],
                    [
                        0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b,
                        0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa
                    ]
                );
        }
        function negate(G1Point memory self) internal pure {
            // The prime q in the base field F_q for G1
            if (self.Y == 0) {
                require(self.X == 0);
                return;
            }
            self.Y = q_mod - self.Y;
        }
        function point_add(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
            point_add_into_dest(p1, p2, r);
            return r;
        }
        function point_add_assign(G1Point memory p1, G1Point memory p2) internal view {
            point_add_into_dest(p1, p2, p1);
        }
        function point_add_into_dest(
            G1Point memory p1,
            G1Point memory p2,
            G1Point memory dest
        ) internal view {
            if (p2.X == 0 && p2.Y == 0) {
                // we add zero, nothing happens
                dest.X = p1.X;
                dest.Y = p1.Y;
                return;
            } else if (p1.X == 0 && p1.Y == 0) {
                // we add into zero, and we add non-zero point
                dest.X = p2.X;
                dest.Y = p2.Y;
                return;
            } else {
                uint256[4] memory input;
                input[0] = p1.X;
                input[1] = p1.Y;
                input[2] = p2.X;
                input[3] = p2.Y;
                bool success = false;
                assembly {
                    success := staticcall(gas(), 6, input, 0x80, dest, 0x40)
                }
                require(success);
            }
        }
        function point_sub_assign(G1Point memory p1, G1Point memory p2) internal view {
            point_sub_into_dest(p1, p2, p1);
        }
        function point_sub_into_dest(
            G1Point memory p1,
            G1Point memory p2,
            G1Point memory dest
        ) internal view {
            if (p2.X == 0 && p2.Y == 0) {
                // we subtracted zero, nothing happens
                dest.X = p1.X;
                dest.Y = p1.Y;
                return;
            } else if (p1.X == 0 && p1.Y == 0) {
                // we subtract from zero, and we subtract non-zero point
                dest.X = p2.X;
                dest.Y = q_mod - p2.Y;
                return;
            } else {
                uint256[4] memory input;
                input[0] = p1.X;
                input[1] = p1.Y;
                input[2] = p2.X;
                input[3] = q_mod - p2.Y;
                bool success = false;
                assembly {
                    success := staticcall(gas(), 6, input, 0x80, dest, 0x40)
                }
                require(success);
            }
        }
        function point_mul(G1Point memory p, Fr memory s) internal view returns (G1Point memory r) {
            point_mul_into_dest(p, s, r);
            return r;
        }
        function point_mul_assign(G1Point memory p, Fr memory s) internal view {
            point_mul_into_dest(p, s, p);
        }
        function point_mul_into_dest(
            G1Point memory p,
            Fr memory s,
            G1Point memory dest
        ) internal view {
            uint256[3] memory input;
            input[0] = p.X;
            input[1] = p.Y;
            input[2] = s.value;
            bool success;
            assembly {
                success := staticcall(gas(), 7, input, 0x60, dest, 0x40)
            }
            require(success);
        }
        function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) {
            require(p1.length == p2.length);
            uint256 elements = p1.length;
            uint256 inputSize = elements * 6;
            uint256[] memory input = new uint256[](inputSize);
            for (uint256 i = 0; i < elements; i++) {
                input[i * 6 + 0] = p1[i].X;
                input[i * 6 + 1] = p1[i].Y;
                input[i * 6 + 2] = p2[i].X[0];
                input[i * 6 + 3] = p2[i].X[1];
                input[i * 6 + 4] = p2[i].Y[0];
                input[i * 6 + 5] = p2[i].Y[1];
            }
            uint256[1] memory out;
            bool success;
            assembly {
                success := staticcall(gas(), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
            }
            require(success);
            return out[0] != 0;
        }
        /// Convenience method for a pairing check for two pairs.
        function pairingProd2(
            G1Point memory a1,
            G2Point memory a2,
            G1Point memory b1,
            G2Point memory b2
        ) internal view returns (bool) {
            G1Point[] memory p1 = new G1Point[](2);
            G2Point[] memory p2 = new G2Point[](2);
            p1[0] = a1;
            p1[1] = b1;
            p2[0] = a2;
            p2[1] = b2;
            return pairing(p1, p2);
        }
    }
    library TranscriptLibrary {
        // flip                    0xe000000000000000000000000000000000000000000000000000000000000000;
        uint256 constant FR_MASK = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
        uint32 constant DST_0 = 0;
        uint32 constant DST_1 = 1;
        uint32 constant DST_CHALLENGE = 2;
        struct Transcript {
            bytes32 state_0;
            bytes32 state_1;
            uint32 challenge_counter;
        }
        function new_transcript() internal pure returns (Transcript memory t) {
            t.state_0 = bytes32(0);
            t.state_1 = bytes32(0);
            t.challenge_counter = 0;
        }
        function update_with_u256(Transcript memory self, uint256 value) internal pure {
            bytes32 old_state_0 = self.state_0;
            self.state_0 = keccak256(abi.encodePacked(DST_0, old_state_0, self.state_1, value));
            self.state_1 = keccak256(abi.encodePacked(DST_1, old_state_0, self.state_1, value));
        }
        function update_with_fr(Transcript memory self, PairingsBn254.Fr memory value) internal pure {
            update_with_u256(self, value.value);
        }
        function update_with_g1(Transcript memory self, PairingsBn254.G1Point memory p) internal pure {
            update_with_u256(self, p.X);
            update_with_u256(self, p.Y);
        }
        function get_challenge(Transcript memory self) internal pure returns (PairingsBn254.Fr memory challenge) {
            bytes32 query = keccak256(abi.encodePacked(DST_CHALLENGE, self.state_0, self.state_1, self.challenge_counter));
            self.challenge_counter += 1;
            challenge = PairingsBn254.Fr({value: uint256(query) & FR_MASK});
        }
    }
    contract Plonk4VerifierWithAccessToDNext {
        uint256 constant r_mod = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
        using PairingsBn254 for PairingsBn254.G1Point;
        using PairingsBn254 for PairingsBn254.G2Point;
        using PairingsBn254 for PairingsBn254.Fr;
        using TranscriptLibrary for TranscriptLibrary.Transcript;
        uint256 constant ZERO = 0;
        uint256 constant ONE = 1;
        uint256 constant TWO = 2;
        uint256 constant THREE = 3;
        uint256 constant FOUR = 4;
        uint256 constant STATE_WIDTH = 4;
        uint256 constant NUM_DIFFERENT_GATES = 2;
        uint256 constant NUM_SETUP_POLYS_FOR_MAIN_GATE = 7;
        uint256 constant NUM_SETUP_POLYS_RANGE_CHECK_GATE = 0;
        uint256 constant ACCESSIBLE_STATE_POLYS_ON_NEXT_STEP = 1;
        uint256 constant NUM_GATE_SELECTORS_OPENED_EXPLICITLY = 1;
        uint256 constant RECURSIVE_CIRCUIT_INPUT_COMMITMENT_MASK =
            0x00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
        uint256 constant LIMB_WIDTH = 68;
        struct VerificationKey {
            uint256 domain_size;
            uint256 num_inputs;
            PairingsBn254.Fr omega;
            PairingsBn254.G1Point[NUM_SETUP_POLYS_FOR_MAIN_GATE + NUM_SETUP_POLYS_RANGE_CHECK_GATE] gate_setup_commitments;
            PairingsBn254.G1Point[NUM_DIFFERENT_GATES] gate_selector_commitments;
            PairingsBn254.G1Point[STATE_WIDTH] copy_permutation_commitments;
            PairingsBn254.Fr[STATE_WIDTH - 1] copy_permutation_non_residues;
            PairingsBn254.G2Point g2_x;
        }
        struct Proof {
            uint256[] input_values;
            PairingsBn254.G1Point[STATE_WIDTH] wire_commitments;
            PairingsBn254.G1Point copy_permutation_grand_product_commitment;
            PairingsBn254.G1Point[STATE_WIDTH] quotient_poly_commitments;
            PairingsBn254.Fr[STATE_WIDTH] wire_values_at_z;
            PairingsBn254.Fr[ACCESSIBLE_STATE_POLYS_ON_NEXT_STEP] wire_values_at_z_omega;
            PairingsBn254.Fr[NUM_GATE_SELECTORS_OPENED_EXPLICITLY] gate_selector_values_at_z;
            PairingsBn254.Fr copy_grand_product_at_z_omega;
            PairingsBn254.Fr quotient_polynomial_at_z;
            PairingsBn254.Fr linearization_polynomial_at_z;
            PairingsBn254.Fr[STATE_WIDTH - 1] permutation_polynomials_at_z;
            PairingsBn254.G1Point opening_at_z_proof;
            PairingsBn254.G1Point opening_at_z_omega_proof;
        }
        struct PartialVerifierState {
            PairingsBn254.Fr alpha;
            PairingsBn254.Fr beta;
            PairingsBn254.Fr gamma;
            PairingsBn254.Fr v;
            PairingsBn254.Fr u;
            PairingsBn254.Fr z;
            PairingsBn254.Fr[] cached_lagrange_evals;
        }
        function evaluate_lagrange_poly_out_of_domain(
            uint256 poly_num,
            uint256 domain_size,
            PairingsBn254.Fr memory omega,
            PairingsBn254.Fr memory at
        ) internal view returns (PairingsBn254.Fr memory res) {
            require(poly_num < domain_size);
            PairingsBn254.Fr memory one = PairingsBn254.new_fr(1);
            PairingsBn254.Fr memory omega_power = omega.pow(poly_num);
            res = at.pow(domain_size);
            res.sub_assign(one);
            require(res.value != 0); // Vanishing polynomial can not be zero at point `at`
            res.mul_assign(omega_power);
            PairingsBn254.Fr memory den = PairingsBn254.copy(at);
            den.sub_assign(omega_power);
            den.mul_assign(PairingsBn254.new_fr(domain_size));
            den = den.inverse();
            res.mul_assign(den);
        }
        function batch_evaluate_lagrange_poly_out_of_domain(
            uint256[] memory poly_nums,
            uint256 domain_size,
            PairingsBn254.Fr memory omega,
            PairingsBn254.Fr memory at
        ) internal view returns (PairingsBn254.Fr[] memory res) {
            PairingsBn254.Fr memory one = PairingsBn254.new_fr(1);
            PairingsBn254.Fr memory tmp_1 = PairingsBn254.new_fr(0);
            PairingsBn254.Fr memory tmp_2 = PairingsBn254.new_fr(domain_size);
            PairingsBn254.Fr memory vanishing_at_z = at.pow(domain_size);
            vanishing_at_z.sub_assign(one);
            // we can not have random point z be in domain
            require(vanishing_at_z.value != 0);
            PairingsBn254.Fr[] memory nums = new PairingsBn254.Fr[](poly_nums.length);
            PairingsBn254.Fr[] memory dens = new PairingsBn254.Fr[](poly_nums.length);
            // numerators in a form omega^i * (z^n - 1)
            // denoms in a form (z - omega^i) * N
            for (uint256 i = 0; i < poly_nums.length; i++) {
                tmp_1 = omega.pow(poly_nums[i]); // power of omega
                nums[i].assign(vanishing_at_z);
                nums[i].mul_assign(tmp_1);
                dens[i].assign(at); // (X - omega^i) * N
                dens[i].sub_assign(tmp_1);
                dens[i].mul_assign(tmp_2); // mul by domain size
            }
            PairingsBn254.Fr[] memory partial_products = new PairingsBn254.Fr[](poly_nums.length);
            partial_products[0].assign(PairingsBn254.new_fr(1));
            for (uint256 i = 1; i < dens.length - 1; i++) {
                partial_products[i].assign(dens[i - 1]);
                partial_products[i].mul_assign(dens[i]);
            }
            tmp_2.assign(partial_products[partial_products.length - 1]);
            tmp_2.mul_assign(dens[dens.length - 1]);
            tmp_2 = tmp_2.inverse(); // tmp_2 contains a^-1 * b^-1 (with! the last one)
            for (uint256 i = dens.length - 1; i < dens.length; i--) {
                dens[i].assign(tmp_2); // all inversed
                dens[i].mul_assign(partial_products[i]); // clear lowest terms
                tmp_2.mul_assign(dens[i]);
            }
            for (uint256 i = 0; i < nums.length; i++) {
                nums[i].mul_assign(dens[i]);
            }
            return nums;
        }
        function evaluate_vanishing(uint256 domain_size, PairingsBn254.Fr memory at)
            internal
            view
            returns (PairingsBn254.Fr memory res)
        {
            res = at.pow(domain_size);
            res.sub_assign(PairingsBn254.new_fr(1));
        }
        function verify_at_z(
            PartialVerifierState memory state,
            Proof memory proof,
            VerificationKey memory vk
        ) internal view returns (bool) {
            PairingsBn254.Fr memory lhs = evaluate_vanishing(vk.domain_size, state.z);
            require(lhs.value != 0); // we can not check a polynomial relationship if point `z` is in the domain
            lhs.mul_assign(proof.quotient_polynomial_at_z);
            PairingsBn254.Fr memory quotient_challenge = PairingsBn254.new_fr(1);
            PairingsBn254.Fr memory rhs = PairingsBn254.copy(proof.linearization_polynomial_at_z);
            // public inputs
            PairingsBn254.Fr memory tmp = PairingsBn254.new_fr(0);
            PairingsBn254.Fr memory inputs_term = PairingsBn254.new_fr(0);
            for (uint256 i = 0; i < proof.input_values.length; i++) {
                tmp.assign(state.cached_lagrange_evals[i]);
                tmp.mul_assign(PairingsBn254.new_fr(proof.input_values[i]));
                inputs_term.add_assign(tmp);
            }
            inputs_term.mul_assign(proof.gate_selector_values_at_z[0]);
            rhs.add_assign(inputs_term);
            // now we need 5th power
            quotient_challenge.mul_assign(state.alpha);
            quotient_challenge.mul_assign(state.alpha);
            quotient_challenge.mul_assign(state.alpha);
            quotient_challenge.mul_assign(state.alpha);
            quotient_challenge.mul_assign(state.alpha);
            PairingsBn254.Fr memory z_part = PairingsBn254.copy(proof.copy_grand_product_at_z_omega);
            for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) {
                tmp.assign(proof.permutation_polynomials_at_z[i]);
                tmp.mul_assign(state.beta);
                tmp.add_assign(state.gamma);
                tmp.add_assign(proof.wire_values_at_z[i]);
                z_part.mul_assign(tmp);
            }
            tmp.assign(state.gamma);
            // we need a wire value of the last polynomial in enumeration
            tmp.add_assign(proof.wire_values_at_z[STATE_WIDTH - 1]);
            z_part.mul_assign(tmp);
            z_part.mul_assign(quotient_challenge);
            rhs.sub_assign(z_part);
            quotient_challenge.mul_assign(state.alpha);
            tmp.assign(state.cached_lagrange_evals[0]);
            tmp.mul_assign(quotient_challenge);
            rhs.sub_assign(tmp);
            return lhs.value == rhs.value;
        }
        function add_contribution_from_range_constraint_gates(
            PartialVerifierState memory state,
            Proof memory proof,
            PairingsBn254.Fr memory current_alpha
        ) internal pure returns (PairingsBn254.Fr memory res) {
            // now add contribution from range constraint gate
            // we multiply selector commitment by all the factors (alpha*(c - 4d)(c - 4d - 1)(..-2)(..-3) + alpha^2 * (4b - c)()()() + {} + {})
            PairingsBn254.Fr memory one_fr = PairingsBn254.new_fr(ONE);
            PairingsBn254.Fr memory two_fr = PairingsBn254.new_fr(TWO);
            PairingsBn254.Fr memory three_fr = PairingsBn254.new_fr(THREE);
            PairingsBn254.Fr memory four_fr = PairingsBn254.new_fr(FOUR);
            res = PairingsBn254.new_fr(0);
            PairingsBn254.Fr memory t0 = PairingsBn254.new_fr(0);
            PairingsBn254.Fr memory t1 = PairingsBn254.new_fr(0);
            PairingsBn254.Fr memory t2 = PairingsBn254.new_fr(0);
            for (uint256 i = 0; i < 3; i++) {
                current_alpha.mul_assign(state.alpha);
                // high - 4*low
                // this is 4*low
                t0 = PairingsBn254.copy(proof.wire_values_at_z[3 - i]);
                t0.mul_assign(four_fr);
                // high
                t1 = PairingsBn254.copy(proof.wire_values_at_z[2 - i]);
                t1.sub_assign(t0);
                // t0 is now t1 - {0,1,2,3}
                // first unroll manually for -0;
                t2 = PairingsBn254.copy(t1);
                // -1
                t0 = PairingsBn254.copy(t1);
                t0.sub_assign(one_fr);
                t2.mul_assign(t0);
                // -2
                t0 = PairingsBn254.copy(t1);
                t0.sub_assign(two_fr);
                t2.mul_assign(t0);
                // -3
                t0 = PairingsBn254.copy(t1);
                t0.sub_assign(three_fr);
                t2.mul_assign(t0);
                t2.mul_assign(current_alpha);
                res.add_assign(t2);
            }
            // now also d_next - 4a
            current_alpha.mul_assign(state.alpha);
            // high - 4*low
            // this is 4*low
            t0 = PairingsBn254.copy(proof.wire_values_at_z[0]);
            t0.mul_assign(four_fr);
            // high
            t1 = PairingsBn254.copy(proof.wire_values_at_z_omega[0]);
            t1.sub_assign(t0);
            // t0 is now t1 - {0,1,2,3}
            // first unroll manually for -0;
            t2 = PairingsBn254.copy(t1);
            // -1
            t0 = PairingsBn254.copy(t1);
            t0.sub_assign(one_fr);
            t2.mul_assign(t0);
            // -2
            t0 = PairingsBn254.copy(t1);
            t0.sub_assign(two_fr);
            t2.mul_assign(t0);
            // -3
            t0 = PairingsBn254.copy(t1);
            t0.sub_assign(three_fr);
            t2.mul_assign(t0);
            t2.mul_assign(current_alpha);
            res.add_assign(t2);
            return res;
        }
        function reconstruct_linearization_commitment(
            PartialVerifierState memory state,
            Proof memory proof,
            VerificationKey memory vk
        ) internal view returns (PairingsBn254.G1Point memory res) {
            // we compute what power of v is used as a delinearization factor in batch opening of
            // commitments. Let's label W(x) = 1 / (x - z) *
            // [
            // t_0(x) + z^n * t_1(x) + z^2n * t_2(x) + z^3n * t_3(x) - t(z)
            // + v (r(x) - r(z))
            // + v^{2..5} * (witness(x) - witness(z))
            // + v^{6} * (selector(x) - selector(z))
            // + v^{7..9} * (permutation(x) - permutation(z))
            // ]
            // W'(x) = 1 / (x - z*omega) *
            // [
            // + v^10 (z(x) - z(z*omega)) <- we need this power
            // + v^11 * (d(x) - d(z*omega))
            // ]
            //
            // we reconstruct linearization polynomial virtual selector
            // for that purpose we first linearize over main gate (over all it's selectors)
            // and multiply them by value(!) of the corresponding main gate selector
            res = PairingsBn254.copy_g1(vk.gate_setup_commitments[STATE_WIDTH + 1]); // index of q_const(x)
            PairingsBn254.G1Point memory tmp_g1 = PairingsBn254.P1();
            PairingsBn254.Fr memory tmp_fr = PairingsBn254.new_fr(0);
            // addition gates
            for (uint256 i = 0; i < STATE_WIDTH; i++) {
                tmp_g1 = vk.gate_setup_commitments[i].point_mul(proof.wire_values_at_z[i]);
                res.point_add_assign(tmp_g1);
            }
            // multiplication gate
            tmp_fr.assign(proof.wire_values_at_z[0]);
            tmp_fr.mul_assign(proof.wire_values_at_z[1]);
            tmp_g1 = vk.gate_setup_commitments[STATE_WIDTH].point_mul(tmp_fr);
            res.point_add_assign(tmp_g1);
            // d_next
            tmp_g1 = vk.gate_setup_commitments[STATE_WIDTH + 2].point_mul(proof.wire_values_at_z_omega[0]); // index of q_d_next(x)
            res.point_add_assign(tmp_g1);
            // multiply by main gate selector(z)
            res.point_mul_assign(proof.gate_selector_values_at_z[0]); // these is only one explicitly opened selector
            PairingsBn254.Fr memory current_alpha = PairingsBn254.new_fr(ONE);
            // calculate scalar contribution from the range check gate
            tmp_fr = add_contribution_from_range_constraint_gates(state, proof, current_alpha);
            tmp_g1 = vk.gate_selector_commitments[1].point_mul(tmp_fr); // selector commitment for range constraint gate * scalar
            res.point_add_assign(tmp_g1);
            // proceed as normal to copy permutation
            current_alpha.mul_assign(state.alpha); // alpha^5
            PairingsBn254.Fr memory alpha_for_grand_product = PairingsBn254.copy(current_alpha);
            // z * non_res * beta + gamma + a
            PairingsBn254.Fr memory grand_product_part_at_z = PairingsBn254.copy(state.z);
            grand_product_part_at_z.mul_assign(state.beta);
            grand_product_part_at_z.add_assign(proof.wire_values_at_z[0]);
            grand_product_part_at_z.add_assign(state.gamma);
            for (uint256 i = 0; i < vk.copy_permutation_non_residues.length; i++) {
                tmp_fr.assign(state.z);
                tmp_fr.mul_assign(vk.copy_permutation_non_residues[i]);
                tmp_fr.mul_assign(state.beta);
                tmp_fr.add_assign(state.gamma);
                tmp_fr.add_assign(proof.wire_values_at_z[i + 1]);
                grand_product_part_at_z.mul_assign(tmp_fr);
            }
            grand_product_part_at_z.mul_assign(alpha_for_grand_product);
            // alpha^n & L_{0}(z), and we bump current_alpha
            current_alpha.mul_assign(state.alpha);
            tmp_fr.assign(state.cached_lagrange_evals[0]);
            tmp_fr.mul_assign(current_alpha);
            grand_product_part_at_z.add_assign(tmp_fr);
            // prefactor for grand_product(x) is complete
            // add to the linearization a part from the term
            // - (a(z) + beta*perm_a + gamma)*()*()*z(z*omega) * beta * perm_d(X)
            PairingsBn254.Fr memory last_permutation_part_at_z = PairingsBn254.new_fr(1);
            for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) {
                tmp_fr.assign(state.beta);
                tmp_fr.mul_assign(proof.permutation_polynomials_at_z[i]);
                tmp_fr.add_assign(state.gamma);
                tmp_fr.add_assign(proof.wire_values_at_z[i]);
                last_permutation_part_at_z.mul_assign(tmp_fr);
            }
            last_permutation_part_at_z.mul_assign(state.beta);
            last_permutation_part_at_z.mul_assign(proof.copy_grand_product_at_z_omega);
            last_permutation_part_at_z.mul_assign(alpha_for_grand_product); // we multiply by the power of alpha from the argument
            // actually multiply prefactors by z(x) and perm_d(x) and combine them
            tmp_g1 = proof.copy_permutation_grand_product_commitment.point_mul(grand_product_part_at_z);
            tmp_g1.point_sub_assign(vk.copy_permutation_commitments[STATE_WIDTH - 1].point_mul(last_permutation_part_at_z));
            res.point_add_assign(tmp_g1);
            // multiply them by v immedately as linearization has a factor of v^1
            res.point_mul_assign(state.v);
            // res now contains contribution from the gates linearization and
            // copy permutation part
            // now we need to add a part that is the rest
            // for z(x*omega):
            // - (a(z) + beta*perm_a + gamma)*()*()*(d(z) + gamma) * z(x*omega)
        }
        function aggregate_commitments(
            PartialVerifierState memory state,
            Proof memory proof,
            VerificationKey memory vk
        ) internal view returns (PairingsBn254.G1Point[2] memory res) {
            PairingsBn254.G1Point memory d = reconstruct_linearization_commitment(state, proof, vk);
            PairingsBn254.Fr memory z_in_domain_size = state.z.pow(vk.domain_size);
            PairingsBn254.G1Point memory tmp_g1 = PairingsBn254.P1();
            PairingsBn254.Fr memory aggregation_challenge = PairingsBn254.new_fr(1);
            PairingsBn254.G1Point memory commitment_aggregation = PairingsBn254.copy_g1(proof.quotient_poly_commitments[0]);
            PairingsBn254.Fr memory tmp_fr = PairingsBn254.new_fr(1);
            for (uint256 i = 1; i < proof.quotient_poly_commitments.length; i++) {
                tmp_fr.mul_assign(z_in_domain_size);
                tmp_g1 = proof.quotient_poly_commitments[i].point_mul(tmp_fr);
                commitment_aggregation.point_add_assign(tmp_g1);
            }
            aggregation_challenge.mul_assign(state.v);
            commitment_aggregation.point_add_assign(d);
            for (uint256 i = 0; i < proof.wire_commitments.length; i++) {
                aggregation_challenge.mul_assign(state.v);
                tmp_g1 = proof.wire_commitments[i].point_mul(aggregation_challenge);
                commitment_aggregation.point_add_assign(tmp_g1);
            }
            for (uint256 i = 0; i < NUM_GATE_SELECTORS_OPENED_EXPLICITLY; i++) {
                aggregation_challenge.mul_assign(state.v);
                tmp_g1 = vk.gate_selector_commitments[0].point_mul(aggregation_challenge);
                commitment_aggregation.point_add_assign(tmp_g1);
            }
            for (uint256 i = 0; i < vk.copy_permutation_commitments.length - 1; i++) {
                aggregation_challenge.mul_assign(state.v);
                tmp_g1 = vk.copy_permutation_commitments[i].point_mul(aggregation_challenge);
                commitment_aggregation.point_add_assign(tmp_g1);
            }
            aggregation_challenge.mul_assign(state.v);
            // now do prefactor for grand_product(x*omega)
            tmp_fr.assign(aggregation_challenge);
            tmp_fr.mul_assign(state.u);
            commitment_aggregation.point_add_assign(proof.copy_permutation_grand_product_commitment.point_mul(tmp_fr));
            aggregation_challenge.mul_assign(state.v);
            tmp_fr.assign(aggregation_challenge);
            tmp_fr.mul_assign(state.u);
            tmp_g1 = proof.wire_commitments[STATE_WIDTH - 1].point_mul(tmp_fr);
            commitment_aggregation.point_add_assign(tmp_g1);
            // collect opening values
            aggregation_challenge = PairingsBn254.new_fr(1);
            PairingsBn254.Fr memory aggregated_value = PairingsBn254.copy(proof.quotient_polynomial_at_z);
            aggregation_challenge.mul_assign(state.v);
            tmp_fr.assign(proof.linearization_polynomial_at_z);
            tmp_fr.mul_assign(aggregation_challenge);
            aggregated_value.add_assign(tmp_fr);
            for (uint256 i = 0; i < proof.wire_values_at_z.length; i++) {
                aggregation_challenge.mul_assign(state.v);
                tmp_fr.assign(proof.wire_values_at_z[i]);
                tmp_fr.mul_assign(aggregation_challenge);
                aggregated_value.add_assign(tmp_fr);
            }
            for (uint256 i = 0; i < proof.gate_selector_values_at_z.length; i++) {
                aggregation_challenge.mul_assign(state.v);
                tmp_fr.assign(proof.gate_selector_values_at_z[i]);
                tmp_fr.mul_assign(aggregation_challenge);
                aggregated_value.add_assign(tmp_fr);
            }
            for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) {
                aggregation_challenge.mul_assign(state.v);
                tmp_fr.assign(proof.permutation_polynomials_at_z[i]);
                tmp_fr.mul_assign(aggregation_challenge);
                aggregated_value.add_assign(tmp_fr);
            }
            aggregation_challenge.mul_assign(state.v);
            tmp_fr.assign(proof.copy_grand_product_at_z_omega);
            tmp_fr.mul_assign(aggregation_challenge);
            tmp_fr.mul_assign(state.u);
            aggregated_value.add_assign(tmp_fr);
            aggregation_challenge.mul_assign(state.v);
            tmp_fr.assign(proof.wire_values_at_z_omega[0]);
            tmp_fr.mul_assign(aggregation_challenge);
            tmp_fr.mul_assign(state.u);
            aggregated_value.add_assign(tmp_fr);
            commitment_aggregation.point_sub_assign(PairingsBn254.P1().point_mul(aggregated_value));
            PairingsBn254.G1Point memory pair_with_generator = commitment_aggregation;
            pair_with_generator.point_add_assign(proof.opening_at_z_proof.point_mul(state.z));
            tmp_fr.assign(state.z);
            tmp_fr.mul_assign(vk.omega);
            tmp_fr.mul_assign(state.u);
            pair_with_generator.point_add_assign(proof.opening_at_z_omega_proof.point_mul(tmp_fr));
            PairingsBn254.G1Point memory pair_with_x = proof.opening_at_z_omega_proof.point_mul(state.u);
            pair_with_x.point_add_assign(proof.opening_at_z_proof);
            pair_with_x.negate();
            res[0] = pair_with_generator;
            res[1] = pair_with_x;
            return res;
        }
        function verify_initial(
            PartialVerifierState memory state,
            Proof memory proof,
            VerificationKey memory vk
        ) internal view returns (bool) {
            require(proof.input_values.length == vk.num_inputs);
            require(vk.num_inputs >= 1);
            TranscriptLibrary.Transcript memory transcript = TranscriptLibrary.new_transcript();
            for (uint256 i = 0; i < vk.num_inputs; i++) {
                transcript.update_with_u256(proof.input_values[i]);
            }
            for (uint256 i = 0; i < proof.wire_commitments.length; i++) {
                transcript.update_with_g1(proof.wire_commitments[i]);
            }
            state.beta = transcript.get_challenge();
            state.gamma = transcript.get_challenge();
            transcript.update_with_g1(proof.copy_permutation_grand_product_commitment);
            state.alpha = transcript.get_challenge();
            for (uint256 i = 0; i < proof.quotient_poly_commitments.length; i++) {
                transcript.update_with_g1(proof.quotient_poly_commitments[i]);
            }
            state.z = transcript.get_challenge();
            uint256[] memory lagrange_poly_numbers = new uint256[](vk.num_inputs);
            for (uint256 i = 0; i < lagrange_poly_numbers.length; i++) {
                lagrange_poly_numbers[i] = i;
            }
            state.cached_lagrange_evals = batch_evaluate_lagrange_poly_out_of_domain(
                lagrange_poly_numbers,
                vk.domain_size,
                vk.omega,
                state.z
            );
            bool valid = verify_at_z(state, proof, vk);
            if (valid == false) {
                return false;
            }
            transcript.update_with_fr(proof.quotient_polynomial_at_z);
            for (uint256 i = 0; i < proof.wire_values_at_z.length; i++) {
                transcript.update_with_fr(proof.wire_values_at_z[i]);
            }
            for (uint256 i = 0; i < proof.wire_values_at_z_omega.length; i++) {
                transcript.update_with_fr(proof.wire_values_at_z_omega[i]);
            }
            transcript.update_with_fr(proof.gate_selector_values_at_z[0]);
            for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) {
                transcript.update_with_fr(proof.permutation_polynomials_at_z[i]);
            }
            transcript.update_with_fr(proof.copy_grand_product_at_z_omega);
            transcript.update_with_fr(proof.linearization_polynomial_at_z);
            state.v = transcript.get_challenge();
            transcript.update_with_g1(proof.opening_at_z_proof);
            transcript.update_with_g1(proof.opening_at_z_omega_proof);
            state.u = transcript.get_challenge();
            return true;
        }
        // This verifier is for a PLONK with a state width 4
        // and main gate equation
        // q_a(X) * a(X) +
        // q_b(X) * b(X) +
        // q_c(X) * c(X) +
        // q_d(X) * d(X) +
        // q_m(X) * a(X) * b(X) +
        // q_constants(X)+
        // q_d_next(X) * d(X*omega)
        // where q_{}(X) are selectors a, b, c, d - state (witness) polynomials
        // q_d_next(X) "peeks" into the next row of the trace, so it takes
        // the same d(X) polynomial, but shifted
        function aggregate_for_verification(Proof memory proof, VerificationKey memory vk)
            internal
            view
            returns (bool valid, PairingsBn254.G1Point[2] memory part)
        {
            PartialVerifierState memory state;
            valid = verify_initial(state, proof, vk);
            if (valid == false) {
                return (valid, part);
            }
            part = aggregate_commitments(state, proof, vk);
            (valid, part);
        }
        function verify(Proof memory proof, VerificationKey memory vk) internal view returns (bool) {
            (bool valid, PairingsBn254.G1Point[2] memory recursive_proof_part) = aggregate_for_verification(proof, vk);
            if (valid == false) {
                return false;
            }
            valid = PairingsBn254.pairingProd2(
                recursive_proof_part[0],
                PairingsBn254.P2(),
                recursive_proof_part[1],
                vk.g2_x
            );
            return valid;
        }
        function verify_recursive(
            Proof memory proof,
            VerificationKey memory vk,
            uint256 recursive_vks_root,
            uint8 max_valid_index,
            uint8[] memory recursive_vks_indexes,
            uint256[] memory individual_vks_inputs,
            uint256[16] memory subproofs_limbs
        ) internal view returns (bool) {
            (uint256 recursive_input, PairingsBn254.G1Point[2] memory aggregated_g1s) = reconstruct_recursive_public_input(
                recursive_vks_root,
                max_valid_index,
                recursive_vks_indexes,
                individual_vks_inputs,
                subproofs_limbs
            );
            assert(recursive_input == proof.input_values[0]);
            (bool valid, PairingsBn254.G1Point[2] memory recursive_proof_part) = aggregate_for_verification(proof, vk);
            if (valid == false) {
                return false;
            }
            // aggregated_g1s = inner
            // recursive_proof_part = outer
            PairingsBn254.G1Point[2] memory combined = combine_inner_and_outer(aggregated_g1s, recursive_proof_part);
            valid = PairingsBn254.pairingProd2(combined[0], PairingsBn254.P2(), combined[1], vk.g2_x);
            return valid;
        }
        function combine_inner_and_outer(PairingsBn254.G1Point[2] memory inner, PairingsBn254.G1Point[2] memory outer)
            internal
            view
            returns (PairingsBn254.G1Point[2] memory result)
        {
            // reuse the transcript primitive
            TranscriptLibrary.Transcript memory transcript = TranscriptLibrary.new_transcript();
            transcript.update_with_g1(inner[0]);
            transcript.update_with_g1(inner[1]);
            transcript.update_with_g1(outer[0]);
            transcript.update_with_g1(outer[1]);
            PairingsBn254.Fr memory challenge = transcript.get_challenge();
            // 1 * inner + challenge * outer
            result[0] = PairingsBn254.copy_g1(inner[0]);
            result[1] = PairingsBn254.copy_g1(inner[1]);
            PairingsBn254.G1Point memory tmp = outer[0].point_mul(challenge);
            result[0].point_add_assign(tmp);
            tmp = outer[1].point_mul(challenge);
            result[1].point_add_assign(tmp);
            return result;
        }
        function reconstruct_recursive_public_input(
            uint256 recursive_vks_root,
            uint8 max_valid_index,
            uint8[] memory recursive_vks_indexes,
            uint256[] memory individual_vks_inputs,
            uint256[16] memory subproofs_aggregated
        ) internal pure returns (uint256 recursive_input, PairingsBn254.G1Point[2] memory reconstructed_g1s) {
            assert(recursive_vks_indexes.length == individual_vks_inputs.length);
            bytes memory concatenated = abi.encodePacked(recursive_vks_root);
            uint8 index;
            for (uint256 i = 0; i < recursive_vks_indexes.length; i++) {
                index = recursive_vks_indexes[i];
                assert(index <= max_valid_index);
                concatenated = abi.encodePacked(concatenated, index);
            }
            uint256 input;
            for (uint256 i = 0; i < recursive_vks_indexes.length; i++) {
                input = individual_vks_inputs[i];
                assert(input < r_mod);
                concatenated = abi.encodePacked(concatenated, input);
            }
            concatenated = abi.encodePacked(concatenated, subproofs_aggregated);
            bytes32 commitment = sha256(concatenated);
            recursive_input = uint256(commitment) & RECURSIVE_CIRCUIT_INPUT_COMMITMENT_MASK;
            reconstructed_g1s[0] = PairingsBn254.new_g1_checked(
                subproofs_aggregated[0] +
                    (subproofs_aggregated[1] << LIMB_WIDTH) +
                    (subproofs_aggregated[2] << (2 * LIMB_WIDTH)) +
                    (subproofs_aggregated[3] << (3 * LIMB_WIDTH)),
                subproofs_aggregated[4] +
                    (subproofs_aggregated[5] << LIMB_WIDTH) +
                    (subproofs_aggregated[6] << (2 * LIMB_WIDTH)) +
                    (subproofs_aggregated[7] << (3 * LIMB_WIDTH))
            );
            reconstructed_g1s[1] = PairingsBn254.new_g1_checked(
                subproofs_aggregated[8] +
                    (subproofs_aggregated[9] << LIMB_WIDTH) +
                    (subproofs_aggregated[10] << (2 * LIMB_WIDTH)) +
                    (subproofs_aggregated[11] << (3 * LIMB_WIDTH)),
                subproofs_aggregated[12] +
                    (subproofs_aggregated[13] << LIMB_WIDTH) +
                    (subproofs_aggregated[14] << (2 * LIMB_WIDTH)) +
                    (subproofs_aggregated[15] << (3 * LIMB_WIDTH))
            );
            return (recursive_input, reconstructed_g1s);
        }
    }
    contract VerifierWithDeserialize is Plonk4VerifierWithAccessToDNext {
        uint256 constant SERIALIZED_PROOF_LENGTH = 34;
        function deserialize_proof(uint256[] memory public_inputs, uint256[] memory serialized_proof)
            internal
            pure
            returns (Proof memory proof)
        {
            require(serialized_proof.length == SERIALIZED_PROOF_LENGTH);
            proof.input_values = new uint256[](public_inputs.length);
            for (uint256 i = 0; i < public_inputs.length; i++) {
                proof.input_values[i] = public_inputs[i];
            }
            uint256 j = 0;
            for (uint256 i = 0; i < STATE_WIDTH; i++) {
                proof.wire_commitments[i] = PairingsBn254.new_g1_checked(serialized_proof[j], serialized_proof[j + 1]);
                j += 2;
            }
            proof.copy_permutation_grand_product_commitment = PairingsBn254.new_g1_checked(
                serialized_proof[j],
                serialized_proof[j + 1]
            );
            j += 2;
            for (uint256 i = 0; i < STATE_WIDTH; i++) {
                proof.quotient_poly_commitments[i] = PairingsBn254.new_g1_checked(
                    serialized_proof[j],
                    serialized_proof[j + 1]
                );
                j += 2;
            }
            for (uint256 i = 0; i < STATE_WIDTH; i++) {
                proof.wire_values_at_z[i] = PairingsBn254.new_fr(serialized_proof[j]);
                j += 1;
            }
            for (uint256 i = 0; i < proof.wire_values_at_z_omega.length; i++) {
                proof.wire_values_at_z_omega[i] = PairingsBn254.new_fr(serialized_proof[j]);
                j += 1;
            }
            for (uint256 i = 0; i < proof.gate_selector_values_at_z.length; i++) {
                proof.gate_selector_values_at_z[i] = PairingsBn254.new_fr(serialized_proof[j]);
                j += 1;
            }
            for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) {
                proof.permutation_polynomials_at_z[i] = PairingsBn254.new_fr(serialized_proof[j]);
                j += 1;
            }
            proof.copy_grand_product_at_z_omega = PairingsBn254.new_fr(serialized_proof[j]);
            j += 1;
            proof.quotient_polynomial_at_z = PairingsBn254.new_fr(serialized_proof[j]);
            j += 1;
            proof.linearization_polynomial_at_z = PairingsBn254.new_fr(serialized_proof[j]);
            j += 1;
            proof.opening_at_z_proof = PairingsBn254.new_g1_checked(serialized_proof[j], serialized_proof[j + 1]);
            j += 2;
            proof.opening_at_z_omega_proof = PairingsBn254.new_g1_checked(serialized_proof[j], serialized_proof[j + 1]);
        }
        function verify_serialized_proof(
            uint256[] memory public_inputs,
            uint256[] memory serialized_proof,
            VerificationKey memory vk
        ) public view returns (bool) {
            require(vk.num_inputs == public_inputs.length);
            Proof memory proof = deserialize_proof(public_inputs, serialized_proof);
            bool valid = verify(proof, vk);
            return valid;
        }
        function verify_serialized_proof_with_recursion(
            uint256[] memory public_inputs,
            uint256[] memory serialized_proof,
            uint256 recursive_vks_root,
            uint8 max_valid_index,
            uint8[] memory recursive_vks_indexes,
            uint256[] memory individual_vks_inputs,
            uint256[16] memory subproofs_limbs,
            VerificationKey memory vk
        ) public view returns (bool) {
            require(vk.num_inputs == public_inputs.length);
            Proof memory proof = deserialize_proof(public_inputs, serialized_proof);
            bool valid = verify_recursive(
                proof,
                vk,
                recursive_vks_root,
                max_valid_index,
                recursive_vks_indexes,
                individual_vks_inputs,
                subproofs_limbs
            );
            return valid;
        }
    }
    contract Plonk4VerifierWithAccessToDNextOld {
        using PairingsBn254 for PairingsBn254.G1Point;
        using PairingsBn254 for PairingsBn254.G2Point;
        using PairingsBn254 for PairingsBn254.Fr;
        using TranscriptLibrary for TranscriptLibrary.Transcript;
        uint256 constant STATE_WIDTH_OLD = 4;
        uint256 constant ACCESSIBLE_STATE_POLYS_ON_NEXT_STEP_OLD = 1;
        struct VerificationKeyOld {
            uint256 domain_size;
            uint256 num_inputs;
            PairingsBn254.Fr omega;
            PairingsBn254.G1Point[STATE_WIDTH_OLD + 2] selector_commitments; // STATE_WIDTH for witness + multiplication + constant
            PairingsBn254.G1Point[ACCESSIBLE_STATE_POLYS_ON_NEXT_STEP_OLD] next_step_selector_commitments;
            PairingsBn254.G1Point[STATE_WIDTH_OLD] permutation_commitments;
            PairingsBn254.Fr[STATE_WIDTH_OLD - 1] permutation_non_residues;
            PairingsBn254.G2Point g2_x;
        }
        struct ProofOld {
            uint256[] input_values;
            PairingsBn254.G1Point[STATE_WIDTH_OLD] wire_commitments;
            PairingsBn254.G1Point grand_product_commitment;
            PairingsBn254.G1Point[STATE_WIDTH_OLD] quotient_poly_commitments;
            PairingsBn254.Fr[STATE_WIDTH_OLD] wire_values_at_z;
            PairingsBn254.Fr[ACCESSIBLE_STATE_POLYS_ON_NEXT_STEP_OLD] wire_values_at_z_omega;
            PairingsBn254.Fr grand_product_at_z_omega;
            PairingsBn254.Fr quotient_polynomial_at_z;
            PairingsBn254.Fr linearization_polynomial_at_z;
            PairingsBn254.Fr[STATE_WIDTH_OLD - 1] permutation_polynomials_at_z;
            PairingsBn254.G1Point opening_at_z_proof;
            PairingsBn254.G1Point opening_at_z_omega_proof;
        }
        struct PartialVerifierStateOld {
            PairingsBn254.Fr alpha;
            PairingsBn254.Fr beta;
            PairingsBn254.Fr gamma;
            PairingsBn254.Fr v;
            PairingsBn254.Fr u;
            PairingsBn254.Fr z;
            PairingsBn254.Fr[] cached_lagrange_evals;
        }
        function evaluate_lagrange_poly_out_of_domain_old(
            uint256 poly_num,
            uint256 domain_size,
            PairingsBn254.Fr memory omega,
            PairingsBn254.Fr memory at
        ) internal view returns (PairingsBn254.Fr memory res) {
            require(poly_num < domain_size);
            PairingsBn254.Fr memory one = PairingsBn254.new_fr(1);
            PairingsBn254.Fr memory omega_power = omega.pow(poly_num);
            res = at.pow(domain_size);
            res.sub_assign(one);
            require(res.value != 0); // Vanishing polynomial can not be zero at point `at`
            res.mul_assign(omega_power);
            PairingsBn254.Fr memory den = PairingsBn254.copy(at);
            den.sub_assign(omega_power);
            den.mul_assign(PairingsBn254.new_fr(domain_size));
            den = den.inverse();
            res.mul_assign(den);
        }
        function batch_evaluate_lagrange_poly_out_of_domain_old(
            uint256[] memory poly_nums,
            uint256 domain_size,
            PairingsBn254.Fr memory omega,
            PairingsBn254.Fr memory at
        ) internal view returns (PairingsBn254.Fr[] memory res) {
            PairingsBn254.Fr memory one = PairingsBn254.new_fr(1);
            PairingsBn254.Fr memory tmp_1 = PairingsBn254.new_fr(0);
            PairingsBn254.Fr memory tmp_2 = PairingsBn254.new_fr(domain_size);
            PairingsBn254.Fr memory vanishing_at_z = at.pow(domain_size);
            vanishing_at_z.sub_assign(one);
            // we can not have random point z be in domain
            require(vanishing_at_z.value != 0);
            PairingsBn254.Fr[] memory nums = new PairingsBn254.Fr[](poly_nums.length);
            PairingsBn254.Fr[] memory dens = new PairingsBn254.Fr[](poly_nums.length);
            // numerators in a form omega^i * (z^n - 1)
            // denoms in a form (z - omega^i) * N
            for (uint256 i = 0; i < poly_nums.length; i++) {
                tmp_1 = omega.pow(poly_nums[i]); // power of omega
                nums[i].assign(vanishing_at_z);
                nums[i].mul_assign(tmp_1);
                dens[i].assign(at); // (X - omega^i) * N
                dens[i].sub_assign(tmp_1);
                dens[i].mul_assign(tmp_2); // mul by domain size
            }
            PairingsBn254.Fr[] memory partial_products = new PairingsBn254.Fr[](poly_nums.length);
            partial_products[0].assign(PairingsBn254.new_fr(1));
            for (uint256 i = 1; i < dens.length - 1; i++) {
                partial_products[i].assign(dens[i - 1]);
                partial_products[i].mul_assign(dens[i]);
            }
            tmp_2.assign(partial_products[partial_products.length - 1]);
            tmp_2.mul_assign(dens[dens.length - 1]);
            tmp_2 = tmp_2.inverse(); // tmp_2 contains a^-1 * b^-1 (with! the last one)
            for (uint256 i = dens.length - 1; i < dens.length; i--) {
                dens[i].assign(tmp_2); // all inversed
                dens[i].mul_assign(partial_products[i]); // clear lowest terms
                tmp_2.mul_assign(dens[i]);
            }
            for (uint256 i = 0; i < nums.length; i++) {
                nums[i].mul_assign(dens[i]);
            }
            return nums;
        }
        function evaluate_vanishing_old(uint256 domain_size, PairingsBn254.Fr memory at)
            internal
            view
            returns (PairingsBn254.Fr memory res)
        {
            res = at.pow(domain_size);
            res.sub_assign(PairingsBn254.new_fr(1));
        }
        function verify_at_z(
            PartialVerifierStateOld memory state,
            ProofOld memory proof,
            VerificationKeyOld memory vk
        ) internal view returns (bool) {
            PairingsBn254.Fr memory lhs = evaluate_vanishing_old(vk.domain_size, state.z);
            require(lhs.value != 0); // we can not check a polynomial relationship if point `z` is in the domain
            lhs.mul_assign(proof.quotient_polynomial_at_z);
            PairingsBn254.Fr memory quotient_challenge = PairingsBn254.new_fr(1);
            PairingsBn254.Fr memory rhs = PairingsBn254.copy(proof.linearization_polynomial_at_z);
            // public inputs
            PairingsBn254.Fr memory tmp = PairingsBn254.new_fr(0);
            for (uint256 i = 0; i < proof.input_values.length; i++) {
                tmp.assign(state.cached_lagrange_evals[i]);
                tmp.mul_assign(PairingsBn254.new_fr(proof.input_values[i]));
                rhs.add_assign(tmp);
            }
            quotient_challenge.mul_assign(state.alpha);
            PairingsBn254.Fr memory z_part = PairingsBn254.copy(proof.grand_product_at_z_omega);
            for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) {
                tmp.assign(proof.permutation_polynomials_at_z[i]);
                tmp.mul_assign(state.beta);
                tmp.add_assign(state.gamma);
                tmp.add_assign(proof.wire_values_at_z[i]);
                z_part.mul_assign(tmp);
            }
            tmp.assign(state.gamma);
            // we need a wire value of the last polynomial in enumeration
            tmp.add_assign(proof.wire_values_at_z[STATE_WIDTH_OLD - 1]);
            z_part.mul_assign(tmp);
            z_part.mul_assign(quotient_challenge);
            rhs.sub_assign(z_part);
            quotient_challenge.mul_assign(state.alpha);
            tmp.assign(state.cached_lagrange_evals[0]);
            tmp.mul_assign(quotient_challenge);
            rhs.sub_assign(tmp);
            return lhs.value == rhs.value;
        }
        function reconstruct_d(
            PartialVerifierStateOld memory state,
            ProofOld memory proof,
            VerificationKeyOld memory vk
        ) internal view returns (PairingsBn254.G1Point memory res) {
            // we compute what power of v is used as a delinearization factor in batch opening of
            // commitments. Let's label W(x) = 1 / (x - z) *
            // [
            // t_0(x) + z^n * t_1(x) + z^2n * t_2(x) + z^3n * t_3(x) - t(z)
            // + v (r(x) - r(z))
            // + v^{2..5} * (witness(x) - witness(z))
            // + v^(6..8) * (permutation(x) - permutation(z))
            // ]
            // W'(x) = 1 / (x - z*omega) *
            // [
            // + v^9 (z(x) - z(z*omega)) <- we need this power
            // + v^10 * (d(x) - d(z*omega))
            // ]
            //
            // we pay a little for a few arithmetic operations to not introduce another constant
            uint256 power_for_z_omega_opening = 1 + 1 + STATE_WIDTH_OLD + STATE_WIDTH_OLD - 1;
            res = PairingsBn254.copy_g1(vk.selector_commitments[STATE_WIDTH_OLD + 1]);
            PairingsBn254.G1Point memory tmp_g1 = PairingsBn254.P1();
            PairingsBn254.Fr memory tmp_fr = PairingsBn254.new_fr(0);
            // addition gates
            for (uint256 i = 0; i < STATE_WIDTH_OLD; i++) {
                tmp_g1 = vk.selector_commitments[i].point_mul(proof.wire_values_at_z[i]);
                res.point_add_assign(tmp_g1);
            }
            // multiplication gate
            tmp_fr.assign(proof.wire_values_at_z[0]);
            tmp_fr.mul_assign(proof.wire_values_at_z[1]);
            tmp_g1 = vk.selector_commitments[STATE_WIDTH_OLD].point_mul(tmp_fr);
            res.point_add_assign(tmp_g1);
            // d_next
            tmp_g1 = vk.next_step_selector_commitments[0].point_mul(proof.wire_values_at_z_omega[0]);
            res.point_add_assign(tmp_g1);
            // z * non_res * beta + gamma + a
            PairingsBn254.Fr memory grand_product_part_at_z = PairingsBn254.copy(state.z);
            grand_product_part_at_z.mul_assign(state.beta);
            grand_product_part_at_z.add_assign(proof.wire_values_at_z[0]);
            grand_product_part_at_z.add_assign(state.gamma);
            for (uint256 i = 0; i < vk.permutation_non_residues.length; i++) {
                tmp_fr.assign(state.z);
                tmp_fr.mul_assign(vk.permutation_non_residues[i]);
                tmp_fr.mul_assign(state.beta);
                tmp_fr.add_assign(state.gamma);
                tmp_fr.add_assign(proof.wire_values_at_z[i + 1]);
                grand_product_part_at_z.mul_assign(tmp_fr);
            }
            grand_product_part_at_z.mul_assign(state.alpha);
            tmp_fr.assign(state.cached_lagrange_evals[0]);
            tmp_fr.mul_assign(state.alpha);
            tmp_fr.mul_assign(state.alpha);
            grand_product_part_at_z.add_assign(tmp_fr);
            PairingsBn254.Fr memory grand_product_part_at_z_omega = state.v.pow(power_for_z_omega_opening);
            grand_product_part_at_z_omega.mul_assign(state.u);
            PairingsBn254.Fr memory last_permutation_part_at_z = PairingsBn254.new_fr(1);
            for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) {
                tmp_fr.assign(state.beta);
                tmp_fr.mul_assign(proof.permutation_polynomials_at_z[i]);
                tmp_fr.add_assign(state.gamma);
                tmp_fr.add_assign(proof.wire_values_at_z[i]);
                last_permutation_part_at_z.mul_assign(tmp_fr);
            }
            last_permutation_part_at_z.mul_assign(state.beta);
            last_permutation_part_at_z.mul_assign(proof.grand_product_at_z_omega);
            last_permutation_part_at_z.mul_assign(state.alpha);
            // add to the linearization
            tmp_g1 = proof.grand_product_commitment.point_mul(grand_product_part_at_z);
            tmp_g1.point_sub_assign(vk.permutation_commitments[STATE_WIDTH_OLD - 1].point_mul(last_permutation_part_at_z));
            res.point_add_assign(tmp_g1);
            res.point_mul_assign(state.v);
            res.point_add_assign(proof.grand_product_commitment.point_mul(grand_product_part_at_z_omega));
        }
        function verify_commitments(
            PartialVerifierStateOld memory state,
            ProofOld memory proof,
            VerificationKeyOld memory vk
        ) internal view returns (bool) {
            PairingsBn254.G1Point memory d = reconstruct_d(state, proof, vk);
            PairingsBn254.Fr memory z_in_domain_size = state.z.pow(vk.domain_size);
            PairingsBn254.G1Point memory tmp_g1 = PairingsBn254.P1();
            PairingsBn254.Fr memory aggregation_challenge = PairingsBn254.new_fr(1);
            PairingsBn254.G1Point memory commitment_aggregation = PairingsBn254.copy_g1(proof.quotient_poly_commitments[0]);
            PairingsBn254.Fr memory tmp_fr = PairingsBn254.new_fr(1);
            for (uint256 i = 1; i < proof.quotient_poly_commitments.length; i++) {
                tmp_fr.mul_assign(z_in_domain_size);
                tmp_g1 = proof.quotient_poly_commitments[i].point_mul(tmp_fr);
                commitment_aggregation.point_add_assign(tmp_g1);
            }
            aggregation_challenge.mul_assign(state.v);
            commitment_aggregation.point_add_assign(d);
            for (uint256 i = 0; i < proof.wire_commitments.length; i++) {
                aggregation_challenge.mul_assign(state.v);
                tmp_g1 = proof.wire_commitments[i].point_mul(aggregation_challenge);
                commitment_aggregation.point_add_assign(tmp_g1);
            }
            for (uint256 i = 0; i < vk.permutation_commitments.length - 1; i++) {
                aggregation_challenge.mul_assign(state.v);
                tmp_g1 = vk.permutation_commitments[i].point_mul(aggregation_challenge);
                commitment_aggregation.point_add_assign(tmp_g1);
            }
            aggregation_challenge.mul_assign(state.v);
            aggregation_challenge.mul_assign(state.v);
            tmp_fr.assign(aggregation_challenge);
            tmp_fr.mul_assign(state.u);
            tmp_g1 = proof.wire_commitments[STATE_WIDTH_OLD - 1].point_mul(tmp_fr);
            commitment_aggregation.point_add_assign(tmp_g1);
            // collect opening values
            aggregation_challenge = PairingsBn254.new_fr(1);
            PairingsBn254.Fr memory aggregated_value = PairingsBn254.copy(proof.quotient_polynomial_at_z);
            aggregation_challenge.mul_assign(state.v);
            tmp_fr.assign(proof.linearization_polynomial_at_z);
            tmp_fr.mul_assign(aggregation_challenge);
            aggregated_value.add_assign(tmp_fr);
            for (uint256 i = 0; i < proof.wire_values_at_z.length; i++) {
                aggregation_challenge.mul_assign(state.v);
                tmp_fr.assign(proof.wire_values_at_z[i]);
                tmp_fr.mul_assign(aggregation_challenge);
                aggregated_value.add_assign(tmp_fr);
            }
            for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) {
                aggregation_challenge.mul_assign(state.v);
                tmp_fr.assign(proof.permutation_polynomials_at_z[i]);
                tmp_fr.mul_assign(aggregation_challenge);
                aggregated_value.add_assign(tmp_fr);
            }
            aggregation_challenge.mul_assign(state.v);
            tmp_fr.assign(proof.grand_product_at_z_omega);
            tmp_fr.mul_assign(aggregation_challenge);
            tmp_fr.mul_assign(state.u);
            aggregated_value.add_assign(tmp_fr);
            aggregation_challenge.mul_assign(state.v);
            tmp_fr.assign(proof.wire_values_at_z_omega[0]);
            tmp_fr.mul_assign(aggregation_challenge);
            tmp_fr.mul_assign(state.u);
            aggregated_value.add_assign(tmp_fr);
            commitment_aggregation.point_sub_assign(PairingsBn254.P1().point_mul(aggregated_value));
            PairingsBn254.G1Point memory pair_with_generator = commitment_aggregation;
            pair_with_generator.point_add_assign(proof.opening_at_z_proof.point_mul(state.z));
            tmp_fr.assign(state.z);
            tmp_fr.mul_assign(vk.omega);
            tmp_fr.mul_assign(state.u);
            pair_with_generator.point_add_assign(proof.opening_at_z_omega_proof.point_mul(tmp_fr));
            PairingsBn254.G1Point memory pair_with_x = proof.opening_at_z_omega_proof.point_mul(state.u);
            pair_with_x.point_add_assign(proof.opening_at_z_proof);
            pair_with_x.negate();
            return PairingsBn254.pairingProd2(pair_with_generator, PairingsBn254.P2(), pair_with_x, vk.g2_x);
        }
        function verify_initial(
            PartialVerifierStateOld memory state,
            ProofOld memory proof,
            VerificationKeyOld memory vk
        ) internal view returns (bool) {
            require(proof.input_values.length == vk.num_inputs);
            require(vk.num_inputs >= 1);
            TranscriptLibrary.Transcript memory transcript = TranscriptLibrary.new_transcript();
            for (uint256 i = 0; i < vk.num_inputs; i++) {
                transcript.update_with_u256(proof.input_values[i]);
            }
            for (uint256 i = 0; i < proof.wire_commitments.length; i++) {
                transcript.update_with_g1(proof.wire_commitments[i]);
            }
            state.beta = transcript.get_challenge();
            state.gamma = transcript.get_challenge();
            transcript.update_with_g1(proof.grand_product_commitment);
            state.alpha = transcript.get_challenge();
            for (uint256 i = 0; i < proof.quotient_poly_commitments.length; i++) {
                transcript.update_with_g1(proof.quotient_poly_commitments[i]);
            }
            state.z = transcript.get_challenge();
            uint256[] memory lagrange_poly_numbers = new uint256[](vk.num_inputs);
            for (uint256 i = 0; i < lagrange_poly_numbers.length; i++) {
                lagrange_poly_numbers[i] = i;
            }
            state.cached_lagrange_evals = batch_evaluate_lagrange_poly_out_of_domain_old(
                lagrange_poly_numbers,
                vk.domain_size,
                vk.omega,
                state.z
            );
            bool valid = verify_at_z(state, proof, vk);
            if (valid == false) {
                return false;
            }
            for (uint256 i = 0; i < proof.wire_values_at_z.length; i++) {
                transcript.update_with_fr(proof.wire_values_at_z[i]);
            }
            for (uint256 i = 0; i < proof.wire_values_at_z_omega.length; i++) {
                transcript.update_with_fr(proof.wire_values_at_z_omega[i]);
            }
            for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) {
                transcript.update_with_fr(proof.permutation_polynomials_at_z[i]);
            }
            transcript.update_with_fr(proof.quotient_polynomial_at_z);
            transcript.update_with_fr(proof.linearization_polynomial_at_z);
            transcript.update_with_fr(proof.grand_product_at_z_omega);
            state.v = transcript.get_challenge();
            transcript.update_with_g1(proof.opening_at_z_proof);
            transcript.update_with_g1(proof.opening_at_z_omega_proof);
            state.u = transcript.get_challenge();
            return true;
        }
        // This verifier is for a PLONK with a state width 4
        // and main gate equation
        // q_a(X) * a(X) +
        // q_b(X) * b(X) +
        // q_c(X) * c(X) +
        // q_d(X) * d(X) +
        // q_m(X) * a(X) * b(X) +
        // q_constants(X)+
        // q_d_next(X) * d(X*omega)
        // where q_{}(X) are selectors a, b, c, d - state (witness) polynomials
        // q_d_next(X) "peeks" into the next row of the trace, so it takes
        // the same d(X) polynomial, but shifted
        function verify_old(ProofOld memory proof, VerificationKeyOld memory vk) internal view returns (bool) {
            PartialVerifierStateOld memory state;
            bool valid = verify_initial(state, proof, vk);
            if (valid == false) {
                return false;
            }
            valid = verify_commitments(state, proof, vk);
            return valid;
        }
    }
    contract VerifierWithDeserializeOld is Plonk4VerifierWithAccessToDNextOld {
        uint256 constant SERIALIZED_PROOF_LENGTH_OLD = 33;
        function deserialize_proof_old(uint256[] memory public_inputs, uint256[] memory serialized_proof)
            internal
            pure
            returns (ProofOld memory proof)
        {
            require(serialized_proof.length == SERIALIZED_PROOF_LENGTH_OLD);
            proof.input_values = new uint256[](public_inputs.length);
            for (uint256 i = 0; i < public_inputs.length; i++) {
                proof.input_values[i] = public_inputs[i];
            }
            uint256 j = 0;
            for (uint256 i = 0; i < STATE_WIDTH_OLD; i++) {
                proof.wire_commitments[i] = PairingsBn254.new_g1_checked(serialized_proof[j], serialized_proof[j + 1]);
                j += 2;
            }
            proof.grand_product_commitment = PairingsBn254.new_g1_checked(serialized_proof[j], serialized_proof[j + 1]);
            j += 2;
            for (uint256 i = 0; i < STATE_WIDTH_OLD; i++) {
                proof.quotient_poly_commitments[i] = PairingsBn254.new_g1_checked(
                    serialized_proof[j],
                    serialized_proof[j + 1]
                );
                j += 2;
            }
            for (uint256 i = 0; i < STATE_WIDTH_OLD; i++) {
                proof.wire_values_at_z[i] = PairingsBn254.new_fr(serialized_proof[j]);
                j += 1;
            }
            for (uint256 i = 0; i < proof.wire_values_at_z_omega.length; i++) {
                proof.wire_values_at_z_omega[i] = PairingsBn254.new_fr(serialized_proof[j]);
                j += 1;
            }
            proof.grand_product_at_z_omega = PairingsBn254.new_fr(serialized_proof[j]);
            j += 1;
            proof.quotient_polynomial_at_z = PairingsBn254.new_fr(serialized_proof[j]);
            j += 1;
            proof.linearization_polynomial_at_z = PairingsBn254.new_fr(serialized_proof[j]);
            j += 1;
            for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) {
                proof.permutation_polynomials_at_z[i] = PairingsBn254.new_fr(serialized_proof[j]);
                j += 1;
            }
            proof.opening_at_z_proof = PairingsBn254.new_g1_checked(serialized_proof[j], serialized_proof[j + 1]);
            j += 2;
            proof.opening_at_z_omega_proof = PairingsBn254.new_g1_checked(serialized_proof[j], serialized_proof[j + 1]);
        }
    }
    pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT OR Apache-2.0
    /// @title Interface of the upgradeable contract
    /// @author Matter Labs
    interface Upgradeable {
        /// @notice Upgrades target of upgradeable contract
        /// @param newTarget New target
        /// @param newTargetInitializationParameters New target initialization parameters
        function upgradeTarget(address newTarget, bytes calldata newTargetInitializationParameters) external;
    }