Polygon zkEVM zkROM代码解析

Posted mutourend

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Polygon zkEVM zkROM代码解析相关的知识,希望对你有一定的参考价值。

1. 引言

Polygon zkEVM zkROM代码库为:

zkROM的基本流程为:

  • 1)A:加载输入变量;
  • 2)B:设置batch storage state-tree:batchHash (oldStateRoot) & globalExitRoot;
  • 3)C:循环解析RLP交易;
  • 4)D:循环处理交易;
  • 5)E:batch asserts:localExitRoot、transactions size、batchHashData & globalHash;
  • 6)F:finalize execution

前序博客为:

本文重点包含D步骤。

2. D:循环处理交易

zkROM第四步为循环处理交易:

;;;;;;;;;;;;;;;;;;
;; D - Loop processing transactions
;;      - Load transaction data and interpret it
;;;;;;;;;;;;;;;;;;

txLoop:
        $ => A          :MLOAD(pendingTxs)
        ; 一次处理一笔交易,更新pendingTxs减1
        ; pendingTxs表示 Number of transactions decoded in RLP block
        A-1 => A        :MSTORE(pendingTxs)
        ; 若A为负数,则跳转到processTxsEnd
        A               :JMPN(processTxsEnd)
		; ctxTxToUse表示 First context to be used when processing transactions
        $ => A          :MLOAD(ctxTxToUse) ; Load first context used by transaction
        ; 更新ctxTxToUse加1
        A+1 => CTX      :MSTORE(ctxTxToUse)
		; 跳转到processTx
                        :JMP(processTx)

processTxEnd:
		; 打印该笔交易处理完毕日志,继续处理下一笔交易
        $eventLog(onFinishTx)
                        :JMP(txLoop)

processTxsEnd:

其中processTx表示单笔交易处理流程。

2.1 processTx单笔交易处理

processTx表示单笔交易处理流程为:

  • 1)A:验证ECDSA签名
  • 2)B:验证chainID
  • 3)C:验证nonce并递增nonce
  • 4)D:检查预付cost
  • 5)E:检查交易类型:
    • 5.1)E.1:合约调用交易
      • a)检查待处理的bytecode 与 state-tree hash bytecode是否一致;
      • b)处理bytecode;
      • c)。。。。
    • 5.2)E.2:部署合约交易(交易中的to参数为空)
      • a)计算新的合约地址;
      • b)处理bytecode;
      • c)部署完成:添加state-tree hash byte code和bytecode length。
  • 6)F:处理Gas

若交易中的to参数不为0且小于10,则表示为预编译合约,跳转至selectorPrecompiled,当前Polygon zkEVM仅支持:

  • ECRECOVER
  • IDENTITY
  • MODEXP

这3种预编译合约。

/**
 * Selector precompiled contract to run
 * Current precompiled supported: ECRECOVER, IDENTITY & MODEXP
 * @param A - Precompiled address
 */
selectorPrecompiled: ; 此时A中为交易中的to参数值,即为预编译地址
    A - 2               :JMPN(funcECRECOVER)
    A - 3               :JMPN(callContract)  ;:JMPN(SHA256)
    A - 4               :JMPN(callContract)  ;:JMPN(RIPEMD160)
    A - 5               :JMPN(IDENTITY)
    A - 6               :JMPN(MODEXP)
    A - 7               :JMPN(callContract) ;:JMPN(ECADD)
    A - 8               :JMPN(callContract) ;:JMPN(ECMUL)
    A - 9               :JMPN(callContract) ;:JMPN(ECPAIRING)
    A - 10              :JMPN(callContract) ;:JMPN(BLAKE2F)
processTx:
;;;;;;;;;;;;;;;;;;
;; A - Verify ecdsa signature
;;;;;;;;;;;;;;;;;;
		; 打印日志
        $eventLog(onProcessTx)
        ; 预留足够的STEP以确保能处理单笔交易
        ; Minimum of 100000 steps left to process a tx
        %MAX_CNT_STEPS - STEP - 100000 :JMPN(outOfCounters)
        ; Get sigDataSize
        ; sigDataSize表示 hash position for the ethereum transaction hash
        $ => HASHPOS                     :MLOAD(sigDataSize)

        ; Check keccak counters
        ; HASHKDIGEST操作符为对136取模,确保CNT_KECCAK_F计数器符合上限要求
        HASHPOS                          :MSTORE(arithA)
        136                              :MSTORE(arithB)
                                         :CALL(divARITH)
        $ => B                           :MLOAD(arithRes1)
        %MAX_CNT_KECCAK_F - CNT_KECCAK_F - %MIN_CNT_KECCAK_BATCH => A
        $                                :LT, JMPC(outOfCounters)

        ; Get hash address previously stored in RLP parsing
        ; lastTxHashId表示First hash address to be used when processing transactions
        ; 更新lastTxHashId加1
        $ => E                          :MLOAD(lastTxHashId)
        E+1 => E                        :MSTORE(lastTxHashId)

        ; Check the signature
		; lastHashKIdUsed表示Last hash address used
        $ => A                          :MLOAD(lastHashKIdUsed)
        A + 1                           :MSTORE(lastHashKIdUsed)
        A + 1                           :MSTORE(ecrecover_lastHashIdUsed)
        $ => A                          :HASHKDIGEST(E)
        $ => B                          :MLOAD(txR)
        $ => C                          :MLOAD(txS)
        $ => D                          :MLOAD(txV)
        ; 调用ecrecover获得签名地址 存在 A寄存器中
                                        :CALL(ecrecover)
        ; Check result is non-zero
checkAndSaveFrom:
		; 要求ecrecover获得的签名地址不为0,否则为无效交易。
		; 同时将签名地址存入txSrcAddr和txSrcOriginAddr全局变量中。
		; txSrcOriginAddr表示origin address of a tx
		; txSrcAddr表示 address that sends a transaction 'message.sender'
        0 => B
        A                               :MSTORE(txSrcAddr)
        A                               :MSTORE(txSrcOriginAddr)
        $                               :EQ,JMPC(invalidIntrinsicTx)

;;;;;;;;;
;; Store init state
;;;;;;;;;

        ; Store initial state at the beginning of the transaction
        ; originSR表示State root before processing each transaction
        ; initSR表示state-tree once the initial upfront cost is substracted and nonce is increased
        SR                              :MSTORE(originSR)
        SR                              :MSTORE(initSR)

;;;;;;;;;;;;;;;;;;
;; B - Verify chainID,验证chainID
;;;;;;;;;;;;;;;;;;
		; txChainId表示 transaction parameter: 'chainId'
        $ => A                          :MLOAD(txChainId)                                       ; A: chainId tx
        ; CONST %ZKEVM_CHAINID = 1000
        %ZKEVM_CHAINID => B                                                               ; B: defaultChainId, A: chainId tx
        $                               :EQ,JMPC(endCheckChainId)                               ; If A == B --> endCheckChainId
                                        :JMP(invalidIntrinsicTx)                                         ; If A != B --> invalidIntrinsicTx
endCheckChainId:

;; Reset warm/cold information,更新信息
        $ => A                          :MLOAD(txSrcOriginAddr)
        ; 将ctx.input.touchedAddress置空
        $resetTouchedAddress() ; clean touchedAddresses since there is a new transaction
        ; 将ctx.input.touchedStorageSlots置空
        $resetStorageSlots() ; clean storageSlots since there is a new transaction
        ; 更新ctx.input.touchedAddress为签名者地址
        $touchedAddress(A) ; add tx.origin to touched addresses

;; Set gasPrice global var
		; txGasPriceRLP表示 transaction parameter: 'gasPrice' decoded from the RLP
        $ => A                          :MLOAD(txGasPriceRLP)
        ; txGasPrice表示 transaction parameter: 'gasPrice' global var
        A                               :MSTORE(txGasPrice)
;;;;;;;;;;;;;;;;;;
;; C - Verify and increase nonce
;;;;;;;;;;;;;;;;;;
		; 将交易签名者地址存入A和E中
        $ => A, E                       :MLOAD(txSrcOriginAddr) ; Address of the origin to A and E
        ; CONST %SMT_KEY_NONCE = 1,为SMT CONSTANT KEY
        %SMT_KEY_NONCE => B
        0 => C
        ; 从Storage中读取以签名者地址(A)和SMT_KEY_NONCE(B)
        ; 以及C为key 的Value值,存入A寄存器中
        $ => A                          :SLOAD
        ; txNonce表示 transaction parameter: nonce
        $ => B                          :MLOAD(txNonce)
        ; 若从Storage中读的nonce值 与 交易中的nonce值 相等,则C=1;否则C=0,为无效交易。
        $ => C                          :EQ
        C - 1                           :JMPN(invalidIntrinsicTx) ; Compare nonce state tree with nonce transaction
        ; 断言 B==A
        B                               :ASSERT ; sanity check
        ; 将nonce值加1,再更新到Storage中相应key中。
        A + 1 => D
        ; 此时E中为交易签名账号
        E => A
        %SMT_KEY_NONCE => B
        0 => C
        ; 更新storage中交易签名账号的nonce值,并将更新后的smt root给SR
        $ => SR                         :SSTORE ; Store the nonce plus one

;;;;;;;;;;;;;;;;;;
;; D - Verify upfront cost,检查预付cost
;;;;;;;;;;;;;;;;;;

        ; Verify batch gas limit
        ; txGasLimit表示transaction parameter: 'gas limit'
        $ => B                          :MLOAD(txGasLimit)
        ; Check batch gas limit is not exceeded by transaction
        ; CONST %BATCH_GAS_LIMIT = 30000000
        %BATCH_GAS_LIMIT => A
        ; txGasLimit应大于等于BATCH_GAS_LIMIT ,否则为无效交易
        $                               :LT,JMPC(invalidIntrinsicTx)

        ; Intrinsic gas --> gas Limit >= 21000 + calldata cost + deployment cost
        ; CONST %BASE_TX_GAS = 21000
        %BASE_TX_GAS => E ; Store init intrinsic gas at E
        ; 当交易中to参数为空时,设置了isCreateContract为1;否则为0
        $ => A                          :MLOAD(isCreateContract)
        ; 若交易中to参数为空,则调用addDeploymentGasCost;否则调用getCalldataGasCost
        -A                              :JMPN(addDeploymentGasCost)
                                        :JMP(getCalldataGasCost)

addDeploymentGasCost:
		; CONST %BASE_TX_DEPLOY_GAS = 32000
        E + %BASE_TX_DEPLOY_GAS => E ; Add 32000 if transaction is a create

getCalldataGasCost:
		; txCalldataLen表示 calldata length
        $ => A                          :MLOAD(txCalldataLen)
        0 => B
        ; 若txCalldataLen为0值,则调用endCalldataIntrinsicGas
        $                               :EQ,JMPC(endCalldataIntrinsicGas)

addGas:
		; dataStarts表示 hash position where de transaction 'data' starts in the batchHashData
        $ => HASHPOS                    :MLOAD(dataStarts)
        ; 调用loopBytes之前,初始化C为0
        0 => C
                                        :JMP(loopBytes)
loopBytes:
		; 预留足够的step
        %MAX_CNT_STEPS - STEP - 20 :JMPN(outOfCounters)
        ; 逐个处理txCalldataLen
        A - C - 1                       :JMPN(endCalldataIntrinsicGas)
        ; 此时E寄存器中存储的为累加gas费,用B临时存储
        E => B
        HASHPOS => D
        ; 设D为1
        1 => D
        $ => E                          :MLOAD(batchHashDataId)
         ; 每次从ctx.hashK[batchHashDataId]的HASHPOS位置读取D(1)个字节到D寄存器中
        $ => D                          :HASHK(E)
        ; 再次用E寄存器存储累加gas费
        B => E
        ; C+1,便于下次循环,遍历calldata
        C + 1 => C
        ; 从ctx.hashK[batchHashDataId]的HASHPOS位置读取的1个字节值 小于 1,则加4Gas;否则加16Gas
        D - 1                           :JMPN(add4Gas)
                                        :JMP(add16Gas)

add4Gas:
		; E寄存器中累加gas加4
        E + 4 => E
        ; 继续循环,遍历calldata
                                        :JMP(loopBytes)

add16Gas:
		; E寄存器中累加gas加16
        E + 16 => E
        ; 继续循环,遍历calldata
                                        :JMP(loopBytes)

endCalldataIntrinsicGas:
		; txGasLimit表示 transaction parameter: 'gas limit'
		; 交易参数中附带的gaslimit应足够,应大于上述计算的gas值
        ; Compare gas limit >= intrinsic gas
        $ => A                          :MLOAD(txGasLimit)
        E => B
        ; 若A小于B,则交易无效
        $                               :LT, JMPC(invalidIntrinsicTx)
        ; Store calculated gas for later usage
        ; 将calldata遍历完后累加的Gas值 存储在 gasCalldata全局变量中
        ; gasCalldata表示 gas spent by the calldata
        E                               :MSTORE(gasCalldata)

		; 检查账号余额应足够,大于等于txGasPrice*txGasLimit+转账金额txValue
        ; Check upfront cost: balance >= gas price * gas limit + value
        ; gas price * gas limit
        ; 全局变量txGasPrice表示transaction parameter: 'gasPrice' global var
        $ => B                          :MLOAD(txGasPrice)
        ; 此时A为txGasLimit,transaction parameter: 'gas limit'
        A                               :MSTORE(arithA)
        B                               :MSTORE(arithB)
                                        :CALL(mulARITH)
        ; 将A*B,即txGasPrice*txGasLimit结果给D
        $ => D                          :MLOAD(arithRes1)
        ; Get caller balance
        ; 全局变量txSrcOriginAddr表示 origin address of a tx
        $ => A                          :MLOAD(txSrcOriginAddr)
        ; 用 %SMT_KEY_BALANCE => B 表示可读性更好
        0 => B, C
        ; 以A/B/C寄存器为Key,读取storage smt中相应的值(为相应账号的balance值)
        $ => C                          :SLOAD
        ; (gas price * gas limit) + value
        ; CTX变量txValue表示 transaction parameter: 'value'
        $ => B                          :MLOAD(txValue)
        ; 此时D寄存器值为txGasPrice*txGasLimit
        D                               :MSTORE(arithA)
        B                               :MSTORE(arithB)
                                        :CALL(addARITH)
        ;将txGasPrice*txGasLimit+txValue 值给B
        $ => B                          :MLOAD(arithRes1)
        ; Comparison
        ; 此时C寄存器中为storage中存储的相应账号的balance值
        C => A
        ; 比较若账号balance值 小于 txGasPrice*txGasLimit+txValue,则为无效交易
        $                               :LT, JMPC(invalidIntrinsicTx)

        ; Substract (gas price * gas limit) from caller balance
        ; 此时C寄存器中为storage中存储的相应账号的balance值
        C                               :MSTORE(arithA)
        ; 此时D寄存器值为txGasPrice*txGasLimit
        D                               :MSTORE(arithB)
                                        :CALL(subARITH)
        ; Substracted balance result in D
        ; 账号的balance值 - txGasPrice*txGasLimit,结果存入D
        $ => D                          :MLOAD(arithRes1)
        ; 全局变量txSrcOriginAddr 表示origin address of a tx
        $ => A                          :MLOAD(txSrcOriginAddr)
        0 => B,C
        ; 更新storage中相应账号的balance值为 减去txGasPrice*txGasLimit后的相应账号的balance值,并将更新后的smt root值给SR
        $ => SR                         :SSTORE

        ; Store state root with upfront cost substracted and nonce increased
        ; 更新了nonce值 和 balance值(减去了txGasPrice*txGasLimit) 之后的storage smt root值,存入initSR中
        SR                              :MSTORE(initSR)

        ; Substract intrinsic gas
        ; CTX变量txGasLimit表示 transaction parameter: 'gas limit'
        $ => GAS                        :MLOAD(txGasLimit)
        ; gasCalldata中存储的为 将calldata遍历完后累加的Gas值 
        ; 全局变量gasCalldata表示 gas spent by the calldata
        $ => A                          :MLOAD(gasCalldata)
        ; txGasLimit - gasCalldata,结果存入GAS寄存器中
        GAS - A => GAS

;;;;;;;;;;;;;;;;;;
;; E - Check transaction type
;;;;;;;;;;;;;;;;;;
txType:

        ; Compute deployment address if create contract operation
         ; 当交易中to参数为空时,设置了isCreateContract为1;否则为0
        $ => A                          :MLOAD(isCreateContract)
        ; 若to参数为空,则跳转至getContractAddress,表示为创建合约操作
        ; 跳转至getContractAddress
        0 - A                           :JMPN(getContractAddress)
        ; 若to参数不为空
        ; CTX变量txDestAddr表示 transaction parameter: 'to'
        $ => A                          :MLOAD(txDestAddr)
        ; Add 'to' to touched addresses 
        ; 交易中的to参数追加到ctx.input.touchedAddress中
        $touchedAddress(A)
        ; Check 'to' is zero or precompiled contract
        ; Check zero address since zero address is not a precompiled contract
        ; 若交易中的to参数为0,则表示合约调用,跳转至callContract
        0 => B
        $                               :EQ, JMPC(callContract)
        ; 若交易中的to参数不为0且小于10,则表示为预编译合约,跳转至selectorPrecompiled
        10 => B
        $                               :LT,JMPC(selectorPrecompiled)
        ; 若交易中的to参数既不是0,也不小于10,则跳转至callContract
                                        :JMP(callContract)

;;;;;;;;;;;;;;;;;;
;; E.2 - Deploy contract
;;     - Compute new contract address
;;     - Process bytecode
;;     - End deploy: add state-tree hash bytecode and bytecode length
;;;;;;;;;;;;;;;;;;

;; compute new create address
getContractAddress:
        ; A new hash with position 0 is started
        ; 设置HASHPOS为0,表示将启动新的哈希计算
        0 => HASHPOS
        ; We get a new hashId
        ; 获取新的lastHashKIdUsed到E,并加1后更新lastHashKIdUsed全局变量值
        ; 全局变量lastHashKIdUsed表示Last hash address used
        $ => E                          :MLOAD(lastHashKIdUsed)
        E+1 => E                        :MSTORE(lastHashKIdUsed)
        ; Check if create is with CREATE2 opcode
        ; CTX变量isCreate2表示 flag to determine if a new context comes from a CREATE2 opcode
        $ => A                          :MLOAD(isCreate2)
        ; 若isCreate2为1等非零值,则跳转到create2
        0 - A                           :JMPN(create2)
        ; isCreate2为0。
        ; 加载txNonce给A
        ; CTX变量txNonce表示 transaction parameter: nonce
        $ => A                          :MLOAD(txNonce)
        0x80 => B
        ; 若交易参数nonce值小于0x80,则跳转至nonce1byte
        $                               :LT,JMPC(nonce1byte)
        ; 若交易参数nonce值大于等于0x80
        ; 加载lengthNonce值给C
        ; lengthNonce为签名交易解析时获得的交易nonce值。
        ; CTX变量lengthNonce表示 'nonce' length used when computing a new contract address
        $ => C                          :MLOAD(lengthNonce)
        ; 设D为1
        1 => D
        ; 1 byte length address + 20 bytes address + 1 byte length nonce + C bytes nonce
        ; RLP数组编码,起始范围为0xc0。
        ; 编码的数组结构为[address, nonce]
        ; nonce最大值为64bit,即最多8个字节就够了,
        ; 因此此时RLP数组编码的长度不会大于55个。
        0xc0 + 22 + C                   :HASHK(E) ; 附加RLP数组编码前缀值
        ; 数组中address前缀值,address为20字节长字符串
        ; address字符串长度20小于55,因此前缀值为0x80+0x14=0x94
        0x94                            :HASHK(E) ; 为address字符串的前缀值
        20 => D
        $ => B                          :MLOAD(txSrcAddr)
        B                               :HASHK(E) ; 只取txSrcAddr地址的20个字节附加到哈希输入中
        ; 设D为1
        1 => D
        ; 此时C中为lengthNonce值,即交易中的nonce字节数,不超过8个字节
        ; nonce字符串编码前缀值为0x80+C
        0x80 + C                        :HASHK(E) ; 为nonce字符串的前缀值
        ; 将交易中的nonce字节数给D
        C => D
        ; 此时A为交易中的nonce值
        A                               :HASHK(E) ; 只取交易中nonce值中的lengthNonce个字节附加到哈希输入中
        ; 跳转到endContractAddress
                                        :JMP(endContractAddress)

nonce1byte: ; 针对交易nonce参数只有1个字节的情况
		; 加载交易签名者账号
        $ => A                          :MLOAD(txSrcAddr)
        ; 加载交易中的nonce值
        $ => B                          :MLOAD(txNonce)
        ; 设置D为1
        1 => D
        ; 1 byte length address + 20 bytes address + 1 byte nonce 
        ; RLP数组编码,起始范围为0xc0。
        ; 编码的数组结构为[address, nonce]
        ; 此时nonce值为1个字节
        ; 因此此时RLP数组编码的长度不会大于55个。
        0xc0 + 22                       :HASHK(E) ; 附加RLP数组编码前缀值
        ; 数组中address前缀值,address为20字节长字符串
        ; address字符串长度20小于55,因此前缀值为0x80+0x14=0x94
        0x94                            :HASHK(E)
        20 => D
        A                               :HASHK(E) ; 只取txSrcAddr地址的20个字节附加到哈希输入中
        ; 设置D为1
        1 => D
        ; 若交易中的nonce值为0,则跳转到nonceIs0
        B - 1                           :JMPN(nonceIs0)
        ; 交易中的nonce值为非零值,且为1字节
        B                               :HASHK(E) ; 将1字节的nonce值直接附加到哈希输入中
        ; 跳转到endContractAddress
                                        :JMP(endContractAddress)

nonceIs0:
		; 若相应的nonce值为0,则RLP(0)=0x80
        0x80                            :HASHK(E) ; 将nonce为0的RLP值附加到哈希输入中

endContractAddress:
        ; end contract address hash and get the 20 first bytes
        ; HASHPOS存储的为当前哈希输入的长度
        ; HASHKLEN为对ctx.hashK[E(0)].data(内容为rlp([address,nonce]))进行Keccak256哈希运算
        HASHPOS                         :HASHKLEN(E)
        ; Keccak哈希运算计数器未超标
        %MAX_CNT_KECCAK_F - CNT_KECCAK_F - %MIN_CNT_KECCAK_BATCH - 1 :JMPN(outOfCounters)
        ; 取上面的哈希结果给A
        $ => A                          :HASHKDIGEST(E)
        ; 调用maskAdress。哈希结果为32字节,地址为20字节
        ; maskAddress为将A寄存器中的值与0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn做AND binary运算
        ; 20字节的地址存入A寄存器中
                                        :CALL(maskAddress) ; Mask address to 20 bytes
        ; 将哈希截取的20字节地址存入createContractAddress中
        ; CTX变量createContractAddress表示 address computed of a new contract
        A                               :MSTORE(createContractAddress)
        ; 将哈希截取的20字节地址存入txDestAddr中
        ; CTX变量txDestAddr表示 transaction parameter: 'to'
        A                               :MSTORE(txDestAddr)
        ; 将哈希截取的20字节地址存入storageAddr中
        ; CTX变量storageAddr表示 address which the storage will be modified
        A                               :MSTORE(storageAddr)
        ; TODO: Add check CREATE or deployment with constructor reverted
        ; 跳转到deploy
                                        :JMP(deploy)

;; compute new contract address as CREATE2 spec: keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:] (https://eips.ethereum.org/EIPS/eip-1014)
create2:
		; CTX变量txCalldataLen表示 calldata length
		; 将txCalldataLen给C寄存器
        $ => C                          :MLOAD(txCalldataLen)
        ; CTX变量originCTX表示 The source context of the current context
        ; 将originCTX给CTX寄存器
        $ => CTX                        :MLOAD(originCTX)
        ; CTX变量argsOffsetCall表示 pointer to the init slot where the calldata begins
        ; 将argsOffsetCall给B寄存器
        $ => B                          :MLOAD(argsOffsetCall)

loopCreate2: ; 以C为计数器,每次处理32个,遍历整个calldata
		; 预留足够的STEP和binary操作计数器
        %MAX_CNT_STEPS - STEP - 100 :JMPN(outOfCounters)
        %MAX_CNT_BINARY - CNT_BINARY - 4 :JMPN(outOfCounters)
		; 若C小于1,则跳转至create2end
        C - 1                           :JMPN(create2end)
        ; 若C为1~31,则跳转至endloopCreate2,处理剩余的不足32字节的calldata内容
        C - 32                          :JMPN(endloopCreate2)
        ; 将argsOffsetCall给E寄存器,argsOffsetCall值应小于0x200000
        B => E
        ; MLOAD32表示 从内存中读取32字节值,其中输入E为offset
        ; 输出E为new offset,A为所读取的32字节值
                                        :CALL(MLOAD32)
        ; 调用MLOAD32之后获得的new offset
        E => B
        ; 设D为32
        32 => D
        $ => E                          :MLOAD(lastHashKIdUsed)
        ; 此时A为MLOAD32从内存中读取的32字节
        ; 更新ctx.hashK[lastHashKIdUsed].data,Keccak(...|A|,附加MLOAD32从内存中读取的32字节
        A                               :HASHK(E)
        ; 每次处理32字节,循环处理。
        C - 32 => C
                                        :JMP(loopCreate2)

endloopCreate2: ; 此时C值为1~31
		; 此时B为调用MLOAD32之后获得的new offset
        B => E
        ; MLOADX表示从内存中读取<32字节值
        ; 输入E为offset,C为length,其中C小于32
        ; 输出A为读取的C字节值,E为new offset
                                        :CALL(MLOADX)
        ; 更新D为32-C
        32 - C => D
        ; 此时A为MLOADX从内存中读取的C字节值
        ; SHRarith表示输入为A和D(字节),输出为A>>D => A
                                        :CALL(SHRarith)
        ; 此时C值为1~31
        C => D
        $ => E                          :MLOAD(lastHashKIdUsed)
        ; 更新ctx.hashK[lastHashKIdUsed].data,Keccak(...|A|,附加MLOADX从内存中读取的C个字节
        A                               :HASHK(E)

create2end: ; 所有的txCalldataLen calldata length均已处理完毕
		; 全局变量currentCTX表示 keeps track of the context used
        $ => CTX                        :MLOAD(currentCTX)
        ; 对ctx.hashK[lastHashKIdUsed].data进行Keccak哈希运算
        ; HASHPOS为Keccak函数的输入长度
        HASHPOS                         :HASHKLEN(E)
        ; Check keccak counters
        ; 对于Keccak:incCounter = Math.ceil((ctx.hashK[addr].data.length + 1) / 136)
        HASHPOS                         :MSTORE(arithA)
        136                             :MSTORE(arithB)
                                        :CALL(divARITH)
        ; B为本次Keccak哈希运算对应的incCounter
        $ => B                          :MLOAD(arithRes1)
        ; A为剩余的可用Keccak哈希计数值
        %MAX_CNT_KECCAK_F - CNT_KECCAK_F - %MIN_CNT_KECCAK_BATCH => A
        ; 若A小于B,则计数器溢出,跳转至outOfCounters
        $                               :LT, JMPC(outOfCounters)
        ; 取ctx.hashK[lastHashKIdUsed].data的Keccak结果给C
        $ => C                          :HASHKDIGEST(E)
        ; new hash with position 0 is started
        ; HASHPOS置0,表示新的哈希运算
        0 => HASHPOS
        ; 更新lastHashKIdUsed和E值,加1
        $ => E                          :MLOAD(lastHashKIdUsed)
        E+1 => E                        :MSTORE(lastHashKIdUsed)
        ; 令D为1,表示Keccak输入将附加1个字节
        1 => D
        ; 对ctx.hashK[lastHashKIdUsed].data,Keccak(0xff|
        0xff                            :HASHK(E)
        ; 令D为20,表示Keccak输入将附加20个字节
        20 => D
        ; 对ctx.hashK[lastHashKIdUsed].data,Keccak(0xff | 交易签名者地址 |
        $ => A                          :MLOAD(txSrcAddr)
        A                               :HASHK(E)
        ; 令D为32,表示Keccak输入将附加32个字节
        32 => D
        ; CTX变量salt为CREATE2参数:CREATE2 parameter 'salt' used to compute new contract address
        $ => B                          :MLOAD(salt)
        ; 对ctx.hashK[lastHashKIdUsed].data,Keccak(0xff | 交易签名者地址 | salt |
        B                               :HASHK(E)
        ; 令D为32,表示Keccak输入将附加32个字节
        32 => D
        ; 此时C表示之前calldata的哈希值
        ; 对ctx.hashK[lastHashKIdUsed].data,Keccak(0xff | 交易签名者地址 | salt | Keccak(calldata) |
        C                               :HASHK(E)
        ; 对ctx.hashK[lastHashKIdUsed].data,Keccak(0xff | 交易签名者地址 | salt | Keccak(calldata))做Keccak哈希运算
        HASHPOS                         :HASHKLEN(E)

        ; Check keccak counters
        ; 对于Keccak:incCounter = Math.ceil((ctx.hashK[addr].data.length + 1) / 136)
        HASHPOS                         :MSTORE(arithA)
        136                             :MSTORE(arithB)
                                        :CALL(divARITH)
        ; B为本次Keccak哈希运算对应的incCounter
        $ => B                          :MLOAD(arithRes1)
        ; A为剩余的可用Keccak哈希计数值
        %MAX_CNT_KECCAK_F - CNT_KECCAK_F - %MIN_CNT_KECCAK_BATCH => A
        ; 若A小于B,则计数器溢出,跳转至outOfCounters
        $                               :LT, JMPC(outOfCounters)
		; 取Keccak(0xff | 交易签名者地址 | salt | Keccak(calldata))哈希结果给A
        $ => A                          :HASHKDIGEST(E)
        ; 调用maskAdress。哈希结果为32字节,地址为20字节
        ; maskAddress为将A寄存器中的值与0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn做AND binary运算
        ; 20字节的地址存入A寄存器中
                                        :CALL(maskAddress) ; Mask address to 20 bytes
        ; 将哈希截取的20字节地址存入createContractAddress中
        ; CTX变量createContractAddress表示 address computed of a new contract
        A                               :MSTORE(createContractAddress)
        ; 将哈希截取的20字节地址存入txDestAddr中
        ; CTX变量txDestAddr表示 transaction parameter: 'to'
        A                               :MSTORE(txDestAddr)
        ; 将哈希截取的20字节地址存入storageAddr中
        ; CTX变量storageAddr表示 address which the storage will be modified
        A                               :MSTORE(storageAddr)

;; deploy contract in state-tree
deploy:
        ; add address to touched addresses
        ; 此时A为经哈希运算获得的20字节合约地址
        ; 将合约地址追加到ctx.input.touchedAddress中
        $touchedAddress(A)

        ; check if address is deployable ( nonce == bytecode == 0)
        A => E

        ; read nonce
        0 => C
        ; CONST %SMT_KEY_NONCE = 1
        %SMT_KEY_NONCE => B
        ; 以A/B/C寄存器为Key,读取storage smt中相应的值(为nonce值)
        $ => B                      :SLOAD
        0 => A
        ; 若storage中nonce值大于0,则说明该地址已被使用,跳转到deployAddressCollision
        $                           :LT,JMPC(deployAddressCollision)

        ; read bytecode
        ; 此时E为经哈希运算获得的20字节合约地址
        E => A
        ; CONST %SMT_KEY_SC_CODE = 2
        %SMT_KEY_SC_CODE => B
        ; 以A/B/C寄存器为Key,读取storage smt中相应的值(为合约bytecode值)
        $ => B                      :SLOAD
        0 => A
        ; 若storage中bytecode值大于0,则说明该地址已被使用,跳转到deployAddressCollision
        $                           :LT,JMPC(deployAddressCollision)

        ; set contract nonce to 1
        ; 此时E为经哈希运算获得的20字节合约地址
        E => A
        1 => D
        %SMT_KEY_NONCE => B
        ; 以A/B/C为key(对应nonce),设置storage相应key(nonce)的value值为D(1)
        $ => SR                         :SSTORE
        ; Move balances if value > 0 just before deploy
        ; CTX变量txValue表示 transaction parameter: 'value'
        $ => B                          :MLOAD(txValue)
        0 => A
        zkPC+2 => RR
        ; 若交易参数`value`值大于0,则在部署之前先move balance,跳转到moveBalances
        $                               :LT, JMPC(moveBalances)
        ; 设置PC和SP为0
        0 => PC
        0 => SP
        ; 跳转到readCode
                                        :JMP(readCode)

;; read calldata bytes of a deploy transaction and process them
readDeployBytecode:
        ; check transaction is a deploy transaction
        ; CTX变量isCreate表示 flag to determine if a new context comes from a CREATE opcode
        $ => B                          :MLOAD(isCreate)
        ; 若交易中使用CREATE opcode创建合约,则跳转至readDeployBytecodeCreate
        0 - B                           :JMPN(readDeployBytecodeCreate)
        ; 若交易中使用CREATE2 opcode创建合约,则继续
        ; check enough bytes to read in calldata
        ; CTX变量txCalldataLen表示 calldata length
        $ => B                          :MLOAD(txCalldataLen)
        ; 若calldata length-PC-1 为负数,则跳转到defaultOpCode
        B - PC - 1                      :JMPN(defaultOpCode)
        ; 加载dataStarts值给HASHPOS。
        ; CTX变量dataStarts表示 hash position where de transaction 'data' starts in the batchHashData
        $ => HASHPOS                    :MLOAD(dataStarts)
        HASHPOS + PC => HASHPOS
        $ => E                          :MLOAD(batchHashDataId)
        1 => D ; 读取calldata第一个字节,调用JMP(@mapping_opcodes + RR)处理
        ; 返回ctx.hashK[batchHashDataId].data中HASHPOS开始的D个字节,给RR
        $ => RR                         :HASHK(E)
        $eventLog(onOpcode(RR)) ; 打印读取的D个字节的bytecode信息
        PC + 1 => PC
        ; 跳转到calldata中指定的bytecode操作符进行处理
                                        :JMP(@mapping_opcodes + RR)

; 读取完CREATE2/CREATE操作符之后,进一步读取和处理CREATE/CREATE2 call 的calldata bytes。
;; read calldata bytes of a CREATE/CREATE2 call and process them
readDeployBytecodeCreate:
        $ => E                          :MLOAD(txCalldataLen)
        ; JMP(@mapping_opcodes + RR)中会切换上下文
        ; CTX变量 originCTX表示 The source context of the current context
        $ => CTX                        :MLOAD(originCTX)
        ; check enough bytes to read in memory
        ; 若CREATE2/CREATE操作符之后无内容,则跳转到readDeployBytecodeCreateDefault
        E - PC - 1                      :JMPN(readDeployBytecodeCreateDefault)
		; CTX变量argsOffsetCall表示 pointer to the init slot where the calldata begins
        $ => E                          :MLOAD(argsOffsetCall)
        E + PC => E
        1 => C
        ; 从内存中,以E为offset,读取C个字节;返回的A为读取的内容,E为新的offset。
                                        :CALL(MLOADX)
        ; 全局变量currentCTX表示 keeps track of the context used
        $ => CTX                        :MLOAD(currentCTX)
        31 => D
        ; A>>D => A
                                        :CALL(SHRarith)
        A => RR  ; 从内存中,以E为offset,读取的C个字节内容在A>>31,赋给RR
        $eventLog(onOpcode(RR))
        PC + 1 => PC
        ; 跳转到RR指定的bytecode操作符进行处理
        ; 除opCREATE/opCREATE2等特殊操作符之外,其它操作符(中有JMP(readCode))都会循环每次读取一个字节
                                        :JMP(@mapping_opcodes + RR)

;; handle error no more bytecode to read when call CREATE/CREATE2
readDeployBytecodeCreateDefault:
		; 全局变量currentCTX表示 keeps track of the context used
        $ => CTX                        :MLOAD(currentCTX)
                                        :JMP(defaultOpCode) ; no bytecode treated as 0x00

;;;;;;;;;;;;;;;;;;
;; E.1 - Call contract
;;     - Check bytecode to process against state-tree hash bytecode
;;     - Process bytecode
;;     - End deploy: add state-tree hash bytecode and bytecode length
;;;;;;;;;;;;;;;;;;
callContract:
        ; Move balances if value > 0 just before executing the contract CALL
        ; CTX变量txValue表示 transaction parameter: 'value'
        $ => B                          :MLOAD(txValue)
        0 => A
        zkPC+2 => RR
        ; 若0<B,则跳转到moveBalances
        $                               :LT, JMPC(moveBalances)
        0 => PC
        0 => SP
		; CTX变量txDestAddr表示 transaction parameter: 'to'
        $ => A                          :MLOAD(txDestAddr)
        ; get contract length
        %SMT_KEY_SC_LENGTH => B
        0 => C
        ; 从Storage中读取以A/B/C为key的Value值,存入B寄存器中
        $ => B                          :SLOAD
        ; 将 从Storage中读取以A/B/C为key的Value值 存入 bytecodeLength变量中
        ; CTX变量 bytecodeLength表示 state-tree length bytecode leaf value of the 'to' address
        B                               :MSTORE(bytecodeLength)
        0 => A
        ; 若B==0,则表示to地址不存在的bytecodeLength为0,不存在bytecode,跳转到defaultOpCode
        $                               :EQ, JMPC(defaultOpCode) ;no bytecode
		; CTX变量txDestAddr表示 transaction parameter: 'to'
        $ => A                          :MLOAD(txDestAddr)
        ; get hash contract
        %SMT_KEY_SC_CODE => B
        ; 从Storage中读取以A/B/C为key的Value值,存入A寄存器中
        $ => A                          :SLOAD
        ; 将 从Storage中读取以A/B/C为key的Value值 存入 hashContractTxDestAddr变量中
        ; CTX变量 hashContractTxDestAddr表示 state-tree hash bytecode leaf value of the 'to' address
        A                               :MSTORE(hashContractTxDestAddr)
        0 => HASHPOS
        1 => D
        ; CTX变量 bytecodeLength表示 state-tree length bytecode leaf value of the 'to' address
        $ => B                          :MLOAD(bytecodeLength)

        ; get a new hashPId
        ; 全局变量nextHashPId 表示 Next posidon hash address available
        $ => E                          :MLOAD(nextHashPId)
        ; CTX变量contractHashId表示 hashP address used to store contract bytecode
        E                               :MSTORE(contractHashId)
        ; 更新全局变量nextHashPId, + 1
        E+1                             :MSTORE(nextHashPId)

checkHashBytecodeLoop:
		; 预留足够的STEP
        %MAX_CNT_STEPS - STEP - 10 :JMPN(outOfCounters)
        ; 此时B为bytecodeLength
        ; 若B - 1 - HASHPOS为负数,则跳转到checkHashBytecodeEnd
        B - 1 - HASHPOS                         :JMPN(checkHashBytecodeEnd) ; finish reading bytecode
        ; 此时A为从Storage中读取的hashContractTxDestAddr值
        ; CTX变量 hashContractTxDestAddr表示 state-tree hash bytecode leaf value of the 'to' address
        ; getBytecode(A, HASHPOS, 1)为:根据A获取hashContract,读取ctx.input.contractsBytecode[hashcontract]中的bytecode,
        ; 以HASHPOS为offset,1为len,获取`d = "0x" + bytecode.slice(2+offset*2, 2+offset*2 + len*2); `,将d给A寄存器
        ; 以contractHashId为addr,将A寄存器中D个字节附加到 ctx.hashP[addr].data 中ctx.HASHPOS位置之后
        $getBytecode(A, HASHPOS, 1)           :HASHP(E) ; hash contract bytecode
        ; 循环调用checkHashBytecodeLoop
                                                :JMP(checkHashBytecodeLoop)

checkHashBytecodeEnd:
		; 此时E为contractHashId,以E为addr,对ctx.hashP[addr].data进行Poseidon哈希运算,结果存在ctx.hashP[addr].digest中
        HASHPOS                         :HASHPLEN(E)
       ; 读取ctx.hashP[addr].digest值给E寄存器
        $ => E                          :HASHPDIGEST(E)
        ; check hash computed matches hash in the smt leaf
        $ => A                          :MLOAD(hashContractTxDestAddr)
        ; 断言 ctx.input.contractsBytecode[hashcontract]中的bytecode哈希结果必须与hashcontract值相等
        E                               :ASSERT
        ; 跳转到readCode
                                        :JMP(readCode)

readByteCode: ; 当交易中to参数不为空时,isCreateContract为0,进入本代码段内
		; CTX变量contractHashId表示 hashP address used to store contract bytecode
        $ => E                          :MLOAD(contractHashId) ; hash index
        ; CTX变量txDestAddr表示 transaction parameter: 'to'
        $ => A                          :MLOAD(txDestAddr)
        ; check next byte exist on the bytecode
        ; CTX变量 bytecodeLength表示 state-tree length bytecode leaf value of the 'to' address
        $ => B                          :MLOAD(bytecodeLength)
        ; 若B - PC - 1为负数,则跳转到defaultOpCode
        B - PC - 1                      :JMPN(defaultOpCode) ; no bytecode treated as 0x00
        ; 以PC为HASHPOS
        PC => HASHPOS
        1 => D
        ; 取ctx.hashP[contractHashId].data中HASHPOS往后的D个字节,给RR
        $ => RR                         :HASHP(E)
        ; 打印RR opcode
        $eventLog(onOpcode(RR))
        ; program counter + 1
        PC + 1 => PC
        ; 跳转到RR指定的bytecode操作符进行处理
                                        :JMP(@mapping_opcodes + RR)

readCode:
		; 当交易中to参数为空时,设置了isCreateContract为1;否则为0
        $ => A                          :MLOAD(isCreateContract)
        ; 若isCreateContract为1时,跳转到readDeployBytecode;否则跳转到readByteCode
        0 - A                           :JMPN(readDeployBytecode)
                                        :JMP(readByteCode)

;; Compute and save hash bytecode and bytecode length in the state-tree
endDeploy:
        ; called from `opRETURNDeploy` which has: C --> length, E --> offset
        ; only when first context ends on deploy
        ; If length is 0 do not modify state-tree
        ; 若C<1,则跳转到handleGas,不修改state-tree
        C - 1                           :JMPN(handleGas)
        ; save offset memory and length to compute hash bytecode
        ; 全局变量memOffsetLinearPoseidon 表示 memory offset to read bytes from
        E                               :MSTORE(memOffsetLinearPoseidon)
        ; 全局变量memSizeLinearPoseidon 表示 memory size to read bytes from
        C                               :MSTORE(memSizeLinearPoseidon)
        ; set bytecode length
        ; CTX变量 createContractAddress表示 address computed of a new contract
        $ => A                          :MLOAD(createContractAddress)
        %SMT_KEY_SC_LENGTH => B
        C => D
        0 => C
        ; 更新Storage中以A/B/C为Key,以D(为bytecode length)为value的值
        ; 将更新后的smt new root返回给SR
        $ => SR                         :SSTORE 
        ; CTX变量 createContractAddress表示 address computed of a new contract
        ; CTX变量txDestAddr表示 transaction parameter: 'to'
        ; 将txDestAddr更新为createContractAddress值
        A                               :MSTORE(txDestAddr)
        ; hashPoseidonLinearFromMemory表示,以memOffsetLinearPoseidon为offset,从内存中加载memSizeLinearPoseidon个字节进行Poseidon哈希运算,
        ; 将Poseidon哈希运算的结果给D寄存器,
        ; 同时将所加载的memSizeLinearPoseidon个字节存入ctx.input.contractsBytecode[ctx.hashP[addr].digest]中。
                                        :CALL(hashPoseidonLinearFromMemory)
        ; CTX变量 createContractAddress表示 address computed of a new contract
        $ => A                          :MLOAD(createContractAddress)
        0 => C
        %SMT_KEY_SC_CODE => B
        ; 更新 以A/B/C为key,上面的Poseidon哈希结果(D寄存器)为value
        ; 将更新后的smt new root返回给SR
        $ => SR                         :SSTORE

;;;;;;;;;;;;;;;;;;
;; F - Handle GAS
;;   - Check refund gas
;;   - Return gas not used to caller
;;   - Pay gas to sequencer
;;;;;;;;;;;;;;;;;;

;; compute maximum gas to refund
handleGas:
        0 => A
        ; CTX变量gasRefund 表示keeps track of the transaction gas refund
        $ => B                          :MLOAD(gasRefund)
        ; 若gasRefund小于1,则跳转到refundGas
        B - 1                           :JMPN(refundGas)
        ; 若gasRefund大于等于1
        ; CTX变量txGasLimit 表示 transaction parameter: 'gas limit'
        $ => A                          :MLOAD(txGasLimit)
        ; 令A = txGasLimit - gasRefund
        A - GAS => A
        ; Div operation with Arith
        A                               :MSTORE(arithA)
        2                               :MSTORE(arithB)
                                        :CALL(divARITH)
       ; 令 A = (txGasLimit - gasRefund)/2
        $ => A                          :MLOAD(arithRes1)
        ; 若(txGasLimit - gasRefund)/2-gasRefund 为负数,则跳转到refundGas
        A - B                           :JMPN(refundGas)
        ; 令A为gasRefund
        B => A

;; add remaining gas to transaction origin
refundGas:
		; 令GAS=GAS+gasRefund
        GAS + A => GAS
        ; 令A为GAS
        GAS => A
        ; 全局变量txGasPrice表示 transaction parameter: 'gasPrice' global var
        $ => B                          :MLOAD(txGasPrice)
        ;Mul operation with Arith
        A                               :MSTORE(arithA)
        B                               :MSTORE(arithB)
                                        :CALL(mulARITH)
        ; 令D为GAS*txGasPrice
        $ => D                          :MLOAD(arithRes1)
		; 全局变量txSrcOriginAddr表示 origin address of a tx
        $ => A                          :MLOAD(txSrcOriginAddr)
        ; 用 %SMT_KEY_BALANCE => B 表示可读性更好
        0 => B,C ; balance key smt
        ; 从Storage中加载交易origin address的balance值,给A寄存器
        $ => A                          :SLOAD ; Original Balance in A

        ; Add operation with Arith
        A                               :MSTORE(arithA)
        D                               :MSTORE(arithB)
                                        :CALL(addARITH)
        ; 令D为GAS*txGasPrice+交易origin address在Storage中的balance值
        $ => D                          :MLOAD(arithRes1)
		; 全局变量txSrcOriginAddr表示 origin address of a tx
        $ => A                          :MLOAD(txSrcOriginAddr)
        ; 用 %SMT_KEY_BALANCE => B 表示可读性更好
        0 => B,C                        ; balance key smt
        ; 将Stoarge中交易origin address的balance值更新为“GAS*txGasPrice+交易origin address在Storage中的balance值”
        $ => SR                         :SSTORE


;; Send gas spent to sequencer
sendGasSeq: ;将花费的gas发送给sequencer
		; CTX变量txGasLimit表示 transaction parameter: 'gas limit'
        $ => A          :MLOAD(txGasLimit)
        ; 令A为txGasLimit-GAS
        A - GAS => A
		; 全局变量txGasPrice表示transaction parameter: 'gasPrice' global var
        $ => B          :MLOAD(txGasPrice)
        ; Mul operation with Arith
        A               :MSTORE(arithA)
        B               :MSTORE(arithB)
                        :CALL(mulARITH)
        ; 令D为 (txGasLimit-GAS)*txGasPrice
        $ => D          :MLOAD(arithRes1) ; value to pay the sequencer in D
		; 全局变量sequencerAddr表示 Coinbase address which will receive the fees
        $ => A          :MLOAD(sequencerAddr)
        ; 用 %SMT_KEY_BALANCE => B 表示可读性更好
        0 => B,C ; Balance key smt
        ; 从Storage中加载sequencerAddr的balance给A寄存器
        $ => A          :SLOAD ; Original Balance in A
        ; Add operation with Arith
        A               :MSTORE(arithA)
        D               :MSTORE(arithB)
                        :CALL(addARITH)
        ; 令D为 (txGasLimit-GAS)*txGasPrice+Storage中sequencerAddr的balance
        $ => D          :MLOAD(arithRes1)
        $ => A          :MLOAD(sequencerAddr)
        ; 用 %SMT_KEY_BALANCE => B 表示可读性更好
        0 => B,C ; balance key smt
        ; 更新Storage中sequencerAddr的balance值为“(txGasLimit-GAS)*txGasPrice+Storage中sequencerAddr的balance”
        $ => SR         :SSTORE
        ; 跳转到processTxEnd,表示当前交易处理完毕。继续处理后续交易
                        :JMP(processTxEnd)


;; handle invalid transaction due to intrinsic checks
invalidIntrinsicTx:
		; 打印错误日志
        $eventLog(onError, intrinsic_invalid)
        ; 全局变量originSR表示 State root before processing each transaction
        $ => SR                         :MLOAD(originSR)
        ; 跳转到processTxEnd,表示当前交易处理完毕。继续处理后续交易
                                        :JMP(processTxEnd)

;; handle error no more bytecode to read
defaultOpCode: ; no bytecode treated as 0x00,即执行opSTOP
        $eventLog(onOpcode(0))
                                        :JMP(opSTOP)

其中MLOAD32为:

;Get offset/32 & offset%32
;@in A offset
;@out E offset/32
;@out C offset%32
offsetUtil:
    $A >> 5 => E          ; $A >> 5 -> E (*)
    $A & 0x1F => C        ; $A & 0x1F -> C
    0x0FFFF - E     :JMPN(stackUnderflow)
    31-C            :JMPN(stackUnderflow)
    E*32+C          :ASSERT
                    :RETURN
                    
VAR GLOBAL isMLOADX
; @info get value from memory (< 32 bytes)
; @in E => offset
; @in C => length
; @out A => value
; @out E => new offset
MLOADX:
    32 - C          :JMPN(errorMLOADMSTORE) ; TDDO Should be unreachable! check it
    32 - C - 1      :JMPN(MLOAD32)
    1               :MSTORE(isMLOADX)

; @info get value from memory (32 bytes)
; @in E => offset
; @out A => value
; @out E => new offset
MLOAD32:
	; 调用之前,将某些会复用的寄存器值存入临时变量中
    RR              :MSTORE(tmpZkPC)
    B               :MSTORE(tmpVarB)
    C               :MSTORE(tmpVarC)
    D               :MSTORE(tmpVarD)
    ; 此时E和A寄存器中值均为argsOffsetCall
    ; CTX变量argsOffsetCall表示 pointer to the init slot where the calldata begins
    E => A ; argsOffsetCall的值必须小于0x200000,即最大为0x1fffff
    0x200000 => B
    $               :LT,JMPC(initMLOAD)
                    :JMP(errorMLOADMSTORE)

initMLOAD:
	; E寄存器中值为argsOffsetCall/32,C寄存器值为argsOffsetCall%32
    zkPC+1 => RR    :JMP(offsetUtil)
    ; 若argsOffsetCall不能被32整除,则调用memAlignOptionMLOAD
    -C              :JMPN(memAlignOptionMLOAD)
    $ => A          :MLOAD(MEM:E)
    $ => B          :MLOAD(isMLOADX)
    E*32 => E
    B - 1           :JMPN(offsetMLOAD32)
                    :JMP(sliceA)

memAlignOptionMLOAD:
	; 加载内存中 地址为 E寄存器内值 的值,给A寄存器
    $ => A          :MLOAD(MEM:E)
    ; 加载内存中 地址为 E寄存器内值+1 的值,给A寄存器
    $ => B          :MLOAD(MEM:E+1)
    ; 从A、B寄存器中,以C(argsOffsetCall%32)为偏移量,读取32个字节
    $ => A          :MEM_ALIGN_RD
    ; 此时E*32 + C 结果为argsOffsetCall,存入E寄存器中。
    E*32 + C => E
    ; 读取isMLOADX全局变量值
    $ => B          :MLOAD(isMLOADX)
    ; 若isMLOADX全局变量值为0,则跳转至offsetMLOAD32,
    ; 即取E=E+32=argsOffsetCall+32
    ; 否则,若isMLOADX全局变量值为1,则继续执行下面的sliceA
    B - 1           :JMPN(offsetMLOAD32)

sliceA:
	; 加载临时变量值给C,此时C为txCalldataLen (-32*i) calldata length
    $ => C          :MLOAD(tmpVarC)
    ; 将32-C给D
    32 - C => D
    ; 此时A寄存器中值,为从MEM:E和MEM:E+1内存地址中,以argsOffsetCall%32为偏移量读取的32字节。
    ; SHRarith表示 A>>D => A
    zkPC+1 => RR    :JMP(SHRarith)
    ; SHLarith表示 A<<D => A
    zkPC+1 => RR    :JMP(SHLarith)
    ; 将isMLOADX置零
    0               :MSTORE(isMLOADX)
    ; 
    E*32 + C => E
                    :JMP(endMLOAD)

offsetMLOAD32:
    E + 32 => E

endMLOAD: ; 调用结束,恢复调用开始前的各寄存器值。
    $ => B          :MLOAD(tmpVarB)
    $ => C          :MLOAD(tmpVarC)
    $ => D          :MLOAD(tmpVarD)
    $ => RR         :MLOAD(tmpZkPC)
                    :RETURN

其中moveBalances为:

moveBalances:

;;;;;;;;
; evmCALL (Move Balances)
;;;;;;;;
        ;Check if is a delegate call
        ; CTX变量isDelegateCall表示 flag to determine if a new context comes from a DELEGATECALL opcode
        $ => A                           :MLOAD(isDelegateCall)
        ; 若isDelegateCall为1,则跳转至endMoveBalances,直接返回
        -A                               :JMPN(endMoveBalances)
        ; isDelegateCall为0
        ; Decrement original balance
        $ => A                          :MLOAD(txSrcAddr)
        ; 用 %SMT_KEY_BALANCE => B 表示可读性更好
        0 => B,C                                                                                ; balance key smt
        ; 以A/B/C为key,从storage中读取签名者地址对应的balance
        $ => A                          :SLOAD                                                  ; Original Balance in E
        ; CTX变量txValue表示 transaction parameter: 'value'
        $ => B                          :MLOAD(txValue)                                         ; A = E - C
        ; Check has enough balance to pay the value. In case not, means we are in a CALL/CALLCODE
        ; 若交易message.sender账号balance值 小于 交易中的value参数值,则为无效交易,调用invalidCall
        $                               :LT,JMPC(invalidCall)
        ; A-B => A,A=>D,即将交易message.sender账号balance值 减去 交易中value值,结果存入D
        $ => D                          :SUB                                                    ; originalBalance -value in D
        ; 将交易message.sender账号地址 加载到 A
        $ => A                          :MLOAD(txSrcAddr)
        ; 用 %SMT_KEY_BALANCE => B 表示可读性更好
        0 => B                                                                                  ; balance key smt
        ; 更新storage中message.sender账号 的balance值为减去的结果。
        ; 以A/B/C为key,将D值更新到storage相应的key中。
        $ => SR                         :SSTORE

        ; Increment destination balance
        ; CTX变量storageAddr表示 address which the storage will be modified
        ; 将storageAddr加载到A
        $ => A                          :MLOAD(storageAddr)
        ; 用 %SMT_KEY_BALANCE => B 表示可读性更好
        0 => B                                                                                  ; balance key smt
        ; 从stoarge中读取storageAddr账号的balance值
        $ => A                          :SLOAD                                                  ; Original Balance in E
        ; 加载交易中的value参数值 到 B
        $ => B                          :MLOAD(txValue)                                         ; E = A + C
        ; A+B => A, A => D
        $ => D                          :ADD
        $ => A                          :MLOAD(storageAddr)
        ; 用 %SMT_KEY_BALANCE => B 表示可读性更好
        0 => B,C                        ; balance key smt
        ; 更新storage中storageAddr账号的balance为加法后的结果值,
        ; 同时更新 storage smt root给SR
        $ => SR                         :SSTORE
endMoveBalances:
                                        :RETURN

其中mapping_opcodes为:

/**
 * Map all Ethereum opcodes to its rom address
 * empty opcodes are set to INVALID opcode
 */
mapping_opcodes:
    :JMP(opSTOP)            ; 0x00
    :JMP(opADD)             ; 0x01
    :JMP(opMUL)             ; 0x02
    :JMP(opSUB)             ; 0x03
    :JMP(opDIV)             ; 0x04
    :JMP(opSDIV)            ; 0x05
    :JMP(opMOD)             ; 0x06
    :JMP(opSMOD)            ; 0x07
    :JMP(opADDMOD)          ; 0x08
    :JMP(opMULMOD)          ; 0x09
    :JMP(opEXP)             ; 0x0a
    :JMP(opSIGNEXTEND)      ; 0x0b
    :JMP(opINVALID)         ; 0x0c
    :JMP(opINVALID)         ; 0x0d
    :JMP(opINVALID)         ; 0x0e
    :JMP(opINVALID)         ; 0x0f
    :JMP(opLT)              ; 0x10
    :JMP(opGT)              ; 0x11
    :JMP(opSLT)             ; 0x12
    :JMP(opSGT)             ; 0x13
    :JMP(opEQ)              ; 0x14
    :JMP(opISZERO)          ; 0x15
    :JMP(opAND)             ; 0x16
    :JMP(opOR)              ; 0x17
    :JMP(opXOR)             ; 0x18
    :JMP(opNOT)             ; 0x19
    :JMP(opBYTE)            ; 0x1a
    :JMP(opSHL)             ; 0x1b
    :JMP(opSHR)             ; 0x1c
    :JMP(opSAR)             ; 0x1d
    :JMP(opINVALID)         ; 0x1e
    :JMP(opINVALID)         ; 0x1f
    :JMP(opSHA3)            ; 0x20
    :JMP(opINVALID)         ; 0x21
    :JMP(opINVALID)         ; 0x22
    :JMP(opINVALID)         ; 0x23
    :JMP(opINVALID)         ; 0x24
    :JMP(opINVALID)         ; 0x25
    :JMP(opINVALID)         ; 0x26
    :JMP(opINVALID)         ; 0x27
    :JMP(opINVALID)         ; 0x28
    :JMP(opINVALID)         ; 0x29
    :JMP(opINVALID)         ; 0x2a
    :JMP(opINVALID)         ; 0x2b
    :JMP(opINVALID)         ; 0x2c
    :JMP(opINVALID)         ; 0x2d
    :JMP(opINVALID)         ; 0x2e
    :JMP(opINVALID)         ; 0x2f
    :JMP(opADDRESS)         ; 0x30
    :JMP(opBALANCE)         ; 0x31
    :JMP(opORIGIN)          ; 0x32
    :JMP(opCALLER)          ; 0x33
    :JMP(opCALLVALUE)       ; 0x34
    :JMP(opCALLDATALOAD)    ; 0x35
    :JMP(opCALLDATASIZE)    ; 0x36
    :JMP(opCALLDATACOPY)    ; 0x37
    :JMP(opCODESIZE)        ; 0x38
    :JMP(opCODECOPY)        ; 0x39
    :JMP(opGASPRICE)        ; 0x3a
    :JMP(opEXTCODESIZE)     ; 0x3b
    :JMP(opEXTCODECOPY)     ; 0x3c
    :JMP(opRETURNDATASIZE)  ; 0x3d
    :JMP(opRETURNDATACOPY)  ; 0x3e
    :JMP(opEXTCODEHASH)     ; 0x3f
    :JMP(opBLOCKHASH)       ; 0x40
    :JMP(opCOINBASE)        ; 0x41
    :JMP(opTIMESTAMP)       ; 0x42
    :JMP(opNUMBER)          ; 0x43
    :JMP(opDIFFICULTY)      ; 0x44
    :JMP(opGASLIMIT)        ; 0x45
    :JMP(opCHAINID)         ; 0x46
    :JMP(opSELFBALANCE)     ; 0x47
    :JMP(opINVALID)         ; 0x48
    :JMP(opINVALID)         ; 0x49
    :JMP(opINVALID)         ; 0x4A
    :JMP(opINVALID)         ; 0x4B
    :JMP(opINVALID)         ; 0x4C
    :JMP(opINVALID)         ; 0x4D
    :JMP(opINVALID)         ; 0x4E
    :JMP(opINVALID)         ; 0x4F
    :JMP(opPOP)             ; 0x50
    :JMP(opMLOAD)           ; 0x51
    :JMP(opMSTORE)          ; 0x52
    :JMP(opMSTORE8)         ; 0x53
    :JMP(opSLOAD)           ; 0x54
    :JMP(opSSTORE)          ; 0x55
    :JMP(opJUMP)            ; 0x56
    :JMP(opJUMPI)           ; 0x57
    :JMP(opPC)              ; 0x58
    :JMP(opMSIZE)           ; 0x59
    :JMP(opGAS)             ; 0x5a
    :JMP(opJUMPDEST)        ; 0x5b
    :JMP(opINVALID)         ; 0x5C
    :JMP(opINVALID)         ; 0x5D
    :JMP(opINVALID)         ; 0x5E
    :JMP(opINVALID)         ; 0x5F
    :JMP(opPUSH1)           ; 0x60
    :JMP(opPUSH2)           ; 0x61
    :JMP(opPUSH3)           ; 0x62
    :JMP(opPUSH4)           ; 0x63
    :JMP(opPUSH5)           ; 0x64
    :JMP(opPUSH6)           ; 0x65
    :JMP(opPUSH7)           ; 0x66
    :JMP(opPUSH8)           ; 0x67
    :JMP(opPUSH9)           ; 0x68
    :JMP(opPUSH10)          ; 0x69
    :JMP(opPUSH11)          ; 0x6a
 

以上是关于Polygon zkEVM zkROM代码解析的主要内容,如果未能解决你的问题,请参考以下文章

Polygon zkEVM zkROM代码解析

Polygon zkEVM zkROM代码解析

Polygon zkEVM zkROM代码解析

Polygon zkEVM zkROM代码解析

Polygon zkEVM节点代码解析

Polygon zkEVM PIL编译器——pilcom 代码解析