Polygon zkEVM zkROM代码解析
Posted mutourend
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Polygon zkEVM zkROM代码解析相关的知识,希望对你有一定的参考价值。
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:合约调用交易
- 5.2)E.2:部署合约交易
- 6)F:处理Gas
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 => A
%SMT_KEY_NONCE => B
0 => C
$ => 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费
E => B
HASHPOS => D
1 => D
$ => E :MLOAD(batchHashDataId)
$ => D :HASHK(E)
B => E
C + 1 => C
D - 1 :JMPN(add4Gas)
:JMP(add16Gas)
add4Gas:
E + 4 => E
:JMP(loopBytes)
add16Gas:
E + 16 => E
:JMP(loopBytes)
endCalldataIntrinsicGas:
; Compare gas limit >= intrinsic gas
$ => A :MLOAD(txGasLimit)
E => B
$ :LT, JMPC(invalidIntrinsicTx)
; Store calculated gas for later usage
E :MSTORE(gasCalldata)
; Check upfront cost: balance >= gas price * gas limit + value
; gas price * gas limit
$ => B :MLOAD(txGasPrice)
A :MSTORE(arithA)
B :MSTORE(arithB)
:CALL(mulARITH)
$ => D :MLOAD(arithRes1)
; Get caller balance
$ => A :MLOAD(txSrcOriginAddr)
0 => B, C
$ => C :SLOAD
; (gas price * gas limit) + value
$ => B :MLOAD(txValue)
D :MSTORE(arithA)
B :MSTORE(arithB)
:CALL(addARITH)
$ => B :MLOAD(arithRes1)
; Comparison
C => A
$ :LT, JMPC(invalidIntrinsicTx)
; Substract (gas price * gas limit) from caller balance
C :MSTORE(arithA)
D :MSTORE(arithB)
:CALL(subARITH)
; Substracted balance result in D
$ => D :MLOAD(arithRes1)
$ => A :MLOAD(txSrcOriginAddr)
0 => B,C
$ => SR :SSTORE
; Store state root with upfront cost substracted and nonce increased
SR :MSTORE(initSR)
; Substract intrinsic gas
$ => GAS :MLOAD(txGasLimit)
$ => A :MLOAD(gasCalldata)
GAS - A => GAS
;;;;;;;;;;;;;;;;;;
;; E - Check transaction type
;;;;;;;;;;;;;;;;;;
txType:
; Compute deployment address if create contract operation
$ => A :MLOAD(isCreateContract)
0 - A :JMPN(getContractAddress)
$ => A :MLOAD(txDestAddr)
; Add 'to' to touched addresses
$touchedAddress(A)
; Check 'to' is zero or precompiled contract
; Check zero address since zero address is not a precompiled contract
0 => B
$ :EQ, JMPC(callContract)
10 => B
$ :LT,JMPC(selectorPrecompiled)
: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
0 => HASHPOS
; We get a new hashId
$ => E :MLOAD(lastHashKIdUsed)
E+1 => E :MSTORE(lastHashKIdUsed)
; Check if create is with CREATE2 opcode
$ => A :MLOAD(isCreate2)
0 - A :JMPN(create2)
$ => A :MLOAD(txNonce)
0x80 => B
$ :LT,JMPC(nonce1byte)
$ => C :MLOAD(lengthNonce)
1 => D
; 1 byte length address + 20 bytes address + 1 byte length nonce + C bytes nonce
0xc0 + 22 + C :HASHK(E)
0x94 :HASHK(E)
20 => D
$ => B :MLOAD(txSrcAddr)
B :HASHK(E)
1 => D
0x80 + C :HASHK(E)
C => D
A :HASHK(E)
:JMP(endContractAddress)
nonce1byte:
$ => A :MLOAD(txSrcAddr)
$ => B :MLOAD(txNonce)
1 => D
0xc0 + 22 :HASHK(E)
0x94 :HASHK(E)
20 => D
A :HASHK(E)
1 => D
B - 1 :JMPN(nonceIs0)
B :HASHK(E)
:JMP(endContractAddress)
nonceIs0:
0x80 :HASHK(E)
endContractAddress:
; end contract address hash and get the 20 first bytes
HASHPOS :HASHKLEN(E)
%MAX_CNT_KECCAK_F - CNT_KECCAK_F - %MIN_CNT_KECCAK_BATCH - 1 :JMPN(outOfCounters)
$ => A :HASHKDIGEST(E)
:CALL(maskAddress) ; Mask address to 20 bytes
A :MSTORE(createContractAddress)
A :MSTORE(txDestAddr)
A :MSTORE(storageAddr)
; TODO: Add check CREATE or deployment with constructor reverted
: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:
$ => C :MLOAD(txCalldataLen)
$ => CTX :MLOAD(originCTX)
$ => B :MLOAD(argsOffsetCall)
loopCreate2:
%MAX_CNT_STEPS - STEP - 100 :JMPN(outOfCounters)
%MAX_CNT_BINARY - CNT_BINARY - 4 :JMPN(outOfCounters)
C - 1 :JMPN(create2end)
C - 32 :JMPN(endloopCreate2)
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)
附录:Polygon Hermez 2.0 zkEVM系列博客
- ZK-Rollups工作原理
- Polygon zkEVM——Hermez 2.0简介
- Polygon zkEVM网络节点
- Polygon zkEVM 基本概念
- Polygon zkEVM Prover
- Polygon zkEVM工具——PIL和CIRCOM
- Polygon zkEVM节点代码解析
- Polygon zkEVM的pil-stark Fibonacci状态机初体验
- Polygon zkEVM的pil-stark Fibonacci状态机代码解析
- Polygon zkEVM PIL编译器——pilcom 代码解析
- Polygon zkEVM Arithmetic状态机
- Polygon zkEVM中的常量多项式
- Polygon zkEVM Binary状态机
- Polygon zkEVM Memory状态机
- Polygon zkEVM Memory Align状态机
- Polygon zkEVM zkASM编译器——zkasmcom
- Polygon zkEVM哈希状态机——Keccak-256和Poseidon
- Polygon zkEVM zkASM语法
- Polygon zkEVM可验证计算简单状态机示例
- Polygon zkEVM zkASM 与 以太坊虚拟机opcode 对应集合
- Polygon zkEVM zkROM代码解析(1)
以上是关于Polygon zkEVM zkROM代码解析的主要内容,如果未能解决你的问题,请参考以下文章