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
前序博客为:
- Polygon zkEVM zkROM代码解析(1)(包含A/B/C步骤)
本文重点包含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。
- 5.1)E.1:合约调用交易
- 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代码解析的主要内容,如果未能解决你的问题,请参考以下文章