1. 引言
前序博客为:
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仅支持:
这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)
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
;
:CALL(MLOAD32)
E => B
32 => D
$ => E :MLOAD(lastHashKIdUsed)
A :HASHK(E)
C - 32 => C
:JMP(loopCreate2)
endloopCreate2:
B => E
:CALL(MLOADX)
32 - C => D
:CALL(SHRarith)
C => D
$ => E :MLOAD(lastHashKIdUsed)
A :HASHK(E)
create2end:
$ => CTX :MLOAD(currentCTX)
HASHPOS :HASHKLEN(E)
; Check keccak counters
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)
$ => C :HASHKDIGEST(E)
; new hash with position 0 is started
0 => HASHPOS
$ => E :MLOAD(lastHashKIdUsed)
E+1 => E :MSTORE(lastHashKIdUsed)
1 => D
0xff :HASHK(E)
20 => D
$ => A :MLOAD(txSrcAddr)
A :HASHK(E)
32 => D
$ => B :MLOAD(salt)
B :HASHK(E)
32 => D
C :HASHK(E)
HASHPOS :HASHKLEN(E)
; Check keccak counters
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)
$ => A :HASHKDIGEST(E)
:CALL(maskAddress) ; Mask address to 20 bytes
A :MSTORE(createContractAddress)
A :MSTORE(txDestAddr)
A :MSTORE(storageAddr)
;; deploy contract in state-tree
deploy:
; add address to touched addresses
${touchedAddress(A)}
; check if address is deployable ( nonce == bytecode == 0)
A => E
; read nonce
0 => C
%SMT_KEY_NONCE => B
$ => B :SLOAD
0 => A
$ :LT,JMPC(deployAddressCollision)
; read bytecode
E => A
%SMT_KEY_SC_CODE => B
$ => B :SLOAD
0 => A
$ :LT,JMPC(deployAddressCollision)
; set contract nonce to 1
E => A
1 => D
%SMT_KEY_NONCE => B
$ => SR :SSTORE
; Move balances if value > 0 just before deploy
$ => B :MLOAD(txValue)
0 => A
zkPC+2 => RR
$ :LT, JMPC(moveBalances)
0 => PC
0 => SP
:JMP(readCode)
;; read calldata bytes of a deploy transaction and process them
readDeployBytecode:
; check transaction is a deploy transaction
$ => B :MLOAD(isCreate)
0 - B :JMPN(readDeployBytecodeCreate)
; check enough bytes to read in calldata
$ => B :MLOAD(txCalldataLen)
B - PC - 1 :JMPN(defaultOpCode)
$ => HASHPOS :MLOAD(dataStarts)
HASHPOS + PC => HASHPOS
$ => E :MLOAD(batchHashDataId)
1 => D
$ => RR :HASHK(E)
${eventLog(onOpcode(RR))}
PC + 1 => PC
:JMP(@mapping_opcodes + RR)
;; read calldata bytes of a CREATE/CREATE2 call and process them
readDeployBytecodeCreate:
$ => E :MLOAD(txCalldataLen)
$ => CTX :MLOAD(originCTX)
; check enough bytes to read in memory
E - PC - 1 :JMPN(readDeployBytecodeCreateDefault)
$ => E :MLOAD(argsOffsetCall)
E + PC => E
1 => C
:CALL(MLOADX)
$ => CTX :MLOAD(currentCTX)
31 => D
:CALL(SHRarith)
A => RR
${eventLog(onOpcode(RR))}
PC + 1 => PC
:JMP(@mapping_opcodes + RR)
;; handle error no more bytecode to read when call CREATE/CREATE2
readDeployBytecodeCreateDefault:
$ => CTX :MLOAD(currentCTX)
:JMP(defaultOpCode)
;;;;;;;;;;;;;;;;;;
;; 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
$ => B :MLOAD(txValue)
0 => A
zkPC+2 => RR
$ :LT, JMPC(moveBalances)
0 => PC
0 => SP
$ => A :MLOAD(txDestAddr)
; get contract length
%SMT_KEY_SC_LENGTH => B
0 => C
$ => B :SLOAD
B :MSTORE(bytecodeLength)
0 => A
$ :EQ, JMPC(defaultOpCode) ;no bytecode
$ => A :MLOAD(txDestAddr)
; get hash contract
%SMT_KEY_SC_CODE => B
$ => A :SLOAD
A :MSTORE(hashContractTxDestAddr)
0 => HASHPOS
1 => D
$ => B :MLOAD(bytecodeLength)
; get a new hashPId
$ => E :MLOAD(nextHashPId)
E :MSTORE(contractHashId)
E+1 :MSTORE(nextHashPId)
checkHashBytecodeLoop:
%MAX_CNT_STEPS - STEP - 10 :JMPN(outOfCounters)
B - 1 - HASHPOS :JMPN(checkHashBytecodeEnd) ; finish reading bytecode
${getBytecode(A, HASHPOS, 1)} :HASHP(E) ; hash contract bytecode
:JMP(checkHashBytecodeLoop)
checkHashBytecodeEnd:
HASHPOS :HASHPLEN(E)
$ => E :HASHPDIGEST(E)
; check hash computed matches hash in the smt leaf
$ => A :MLOAD(hashContractTxDestAddr)
E :ASSERT
:JMP(readCode)
readByteCode:
$ => E :MLOAD(contractHashId) ; hash index
$ => A :MLOAD(txDestAddr)
; check next byte exist on the bytecode
$ => B :MLOAD(bytecodeLength)
B - PC - 1 :JMPN(defaultOpCode) ; no bytecode treated as 0x00
PC => HASHPOS
1 => D
$ => RR :HASHP(E)
${eventLog(onOpcode(RR))}
PC + 1 => PC
:JMP(@mapping_opcodes + RR)
readCode:
$ => A :MLOAD(isCreateContract)
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 :JMPN(handleGas)
; save offset memory and length to compute hash bytecode
E :MSTORE(memOffsetLinearPoseidon)
C :MSTORE(memSizeLinearPoseidon)
; set bytecode length
$ => A :MLOAD(createContractAddress)
%SMT_KEY_SC_LENGTH => B
C => D
0 => C
$ => SR :SSTORE
A :MSTORE(txDestAddr)
:CALL(hashPoseidonLinearFromMemory)
$ => A :MLOAD(createContractAddress)
0 => C
%SMT_KEY_SC_CODE => B
$ => 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
$ => B :MLOAD(gasRefund)
B - 1 :JMPN(refundGas)
$ => A :MLOAD(txGasLimit)
A - GAS => A
; Div operation with Arith
A :MSTORE(arithA)
2 :MSTORE(arithB)
:CALL(divARITH)
$ => A :MLOAD(arithRes1)
A - B :JMPN(refundGas)
B => A
;; add remaining gas to transaction origin
refundGas:
GAS + A => GAS
GAS => A
$ => B :MLOAD(txGasPrice)
;Mul operation with Arith
A :MSTORE(arithA)
B :MSTORE(arithB)
:CALL(mulARITH)
$ => D :MLOAD(arithRes1)
$ => A :MLOAD(txSrcOriginAddr)
0 => B,C ; balance key smt
$ => A :SLOAD ; Original Balance in A
; Add operation with Arith
A :MSTORE(arithA)
D :MSTORE(arithB)
:CALL(addARITH)
$ => D :MLOAD(arithRes1)
$ => A :MLOAD(txSrcOriginAddr)
0 => B,C ; balance key smt
$ => SR :SSTORE
;; Send gas spent to sequencer
sendGasSeq:
$ => A :MLOAD(txGasLimit)
A - GAS => A
$ => B :MLOAD(txGasPrice)
; Mul operation with Arith
A :MSTORE(arithA)
B :MSTORE(arithB)
:CALL(mulARITH)
$ => D :MLOAD(arithRes1) ; value to pay the sequencer in D
$ => A :MLOAD(sequencerAddr)
0 => B,C ; Balance key smt
$ => A :SLOAD ; Original Balance in A
; Add operation with Arith
A :MSTORE(arithA)
D :MSTORE(arithB)
:CALL(addARITH)
$ => D :MLOAD(arithRes1)
$ => A :MLOAD(sequencerAddr)
0 => B,C ; balance key smt
$ => SR :SSTORE
:JMP(processTxEnd)
;; handle invalid transaction due to intrinsic checks
invalidIntrinsicTx:
${eventLog(onError, intrinsic_invalid)}
$ => SR :MLOAD(originSR)
:JMP(processTxEnd)
;; handle error no more bytecode to read
defaultOpCode:
${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:
$ => A :MLOAD(MEM:E)
$ => B :MLOAD(MEM:E+1)
$ => A :MEM_ALIGN_RD
E*32 + C => E
$ => B :MLOAD(isMLOADX)
B - 1 :JMPN(offsetMLOAD32)
sliceA:
$ => C :MLOAD(tmpVarC)
32 - C => D
zkPC+1 => RR :JMP(SHRarith)
zkPC+1 => RR :JMP(SHLarith)
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
附录:Polygon Hermez 2.0 zkEVM系列博客
|