Uniswap
部署依赖Istanbul
中的chainid
,从君士坦丁堡升级到YoloV1
最新版本,就可以在链上正常部署。本文讲粗略讲一下升级涉及功能和问题的定位解决。
Istanbul升级
1344中
添加chainid
的opcode
获取链的当前chainid1884
中添加balance
的opcede
查询调用方余额2200
中状态加载和存储的gas
费增加更新YoloV1升级
2315
的subroutines
,包含如下opcode BEGINSUB
,RETURNSUB
,JUMPSUB
功能点
整合gas
各个版本的升级,删除了params
的gas_table
不同版本更新opcode
的gas
费用,提高了状态加载和存储的费用如SLOAD
BALANCE
EXTCODEHASH
常量gas
费从opcode
对应的函数中移除,赋值给constantGas
操作数big.Int
类型为uint256.Int
,提高25%
执行效率
整合内存拷贝
和内存创建
的gas计算
和溢出检查
减少zk
使用的bn256
gas费,添加blake2F
和bls预编译合约
增加ReturnStack
可返回错误结果
方便debug
SLOAD
BALANCE
EXTCODEHASH
存储访问操作码,16年因为EXTCODESIZE
被dos攻击,状态读取和存储异常耗时,需要提高gas费。
之前合约执行错误只会输出execution reverted
,现在可以打印哪里报错
execution reverted: TransferHelper: TRANSFER_FROM_FAILED
在sol
里面找到如下报错,为对方Approve
金额较低导致。
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');
opcode
中gas
费计算错误constantGas
常量已从gas_table
中移除,直接在代码中根据解析器分支使用不同的opcode gas
费dynamicGas
计算在内存占用费用下面规定了不同的分叉使用的EVMInterpreter
解析器是不一样的。
// NewEVMInterpreter returns a new instance of the Interpreter.
func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
// We use the STOP instruction whether to see
// the jump table was initialised. If it was not
// we'll set the default jump table.
if cfg.JumpTable[STOP] == nil {
var jt JumpTable
switch {
case evm.chainRules.IsYoloV1:
jt = yoloV1InstructionSet
case evm.chainRules.IsIstanbul:
jt = istanbulInstructionSet
case evm.chainRules.IsConstantinople:
jt = constantinopleInstructionSet
case evm.chainRules.IsByzantium:
jt = byzantiumInstructionSet
default:
jt = frontierInstructionSet
}
cfg.JumpTable = jt
}
return &EVMInterpreter{
evm: evm,
cfg: cfg,
}
}
不同的升级使用的PrecompiledContract
预编译合约也不一样的,预编译合约主要是处理一些复杂的数学计算,这类计算evm执行起来较慢,所以以预编译合约的形式加入到evm里面,合约执行的过程不是汇编执行而是具体代码
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract
switch {
case evm.chainRules.IsYoloV1:
precompiles = PrecompiledContractsYoloV1
case evm.chainRules.IsIstanbul:
precompiles = PrecompiledContractsIstanbul
case evm.chainRules.IsByzantium:
precompiles = PrecompiledContractsByzantium
default:
precompiles = PrecompiledContractsHomestead
}
p, ok := precompiles[addr]
return p, ok
}
这是升级中加的一些预编译合约,很多是支持零知识证明的,椭圆配对曲线称为BLS12-381,为过渡eth2.0提供基础,该预编译使精简客户端成为可能。
// PrecompiledContractsYoloV1 contains the default set of pre-compiled Ethereum
// contracts used in the Yolo v1 test release.
var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{10}): &bls12381G1Add{},
common.BytesToAddress([]byte{11}): &bls12381G1Mul{},
common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{},
common.BytesToAddress([]byte{13}): &bls12381G2Add{},
common.BytesToAddress([]byte{14}): &bls12381G2Mul{},
common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{},
common.BytesToAddress([]byte{16}): &bls12381Pairing{},
common.BytesToAddress([]byte{17}): &bls12381MapG1{},
common.BytesToAddress([]byte{18}): &bls12381MapG2{},
}
合约执行中如果计算的gas
不一致,区块验证就会报错。这个问题相当严重
抓取合约执行过程的Trace log
分析不同点,截取了如下日志。
{"pc":2373,"op":84,"gas":"0xeddb6","gasCost":"0x320","memory":"0x000000000000000000000000735bce5ecc8455eb9bf8270aa138ce05e069b4c1656502bc70f67e407a17d7e0cba13a8b062e13793501bed56cecc8aee887bd7d0000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x1bf73a65","0x181","0x2de96ad91d62a796c4ba80e0a862520f9c8a7805","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x0","0x47f37265329d36000","0x1","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x1137c4082672bb033743321bcc426da68e2052bc996d994df96a0e1cd48b5b60"],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"SLOAD","error":""}
{"pc":2374,"op":144,"gas":"0xeda96","gasCost":"0x3","memory":"0x000000000000000000000000735bce5ecc8455eb9bf8270aa138ce05e069b4c1656502bc70f67e407a17d7e0cba13a8b062e13793501bed56cecc8aee887bd7d0000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x1bf73a65","0x181","0x2de96ad91d62a796c4ba80e0a862520f9c8a7805","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x0","0x47f37265329d36000","0x1","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x1"],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"SWAP1","error":""}
将上面的0xeddb6
和0xeda96
相减是800
十六进制为0x320
{"pc":2373,"op":84,"gas":"0xeddb6","gasCost":"0xc8","memory":"0x000000000000000000000000735bce5ecc8455eb9bf8270aa138ce05e069b4c1656502bc70f67e407a17d7e0cba13a8b062e13793501bed56cecc8aee887bd7d0000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x1bf73a65","0x181","0x2de96ad91d62a796c4ba80e0a862520f9c8a7805","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x0","0x47f37265329d36000","0x1","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x1137c4082672bb033743321bcc426da68e2052bc996d994df96a0e1cd48b5b60"],"depth":1,"refund":0,"opName":"SLOAD","error":""}
{"pc":2374,"op":144,"gas":"0xedcee","gasCost":"0xc8","memory":"0x000000000000000000000000735bce5ecc8455eb9bf8270aa138ce05e069b4c1656502bc70f67e407a17d7e0cba13a8b062e13793501bed56cecc8aee887bd7d0000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x1bf73a65","0x181","0x2de96ad91d62a796c4ba80e0a862520f9c8a7805","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x0","0x47f37265329d36000","0x1","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x1"],"depth":1,"refund":0,"opName":"SWAP1","error":""}
这个是升级前的日志,相减得200
十六进制为0xc8
查询84
的opcode
是0x54
SLOAD
在Istanbul
后SloadGasEIP1884
调节到了800
,这个由于分叉高度设置太低导致使用yoloV1InstructionSet
解析器,其实是没问题的,调节YoloV1Block
高度即可
"YOLOv1": {
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
YoloV1Block: big.NewInt(0),
},
eth部署合约正确的指令流程
{"pc":23,"op":96,"gas":"0x278abc","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":[],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"PUSH1","error":""}
{"pc":25,"op":81,"gas":"0x278ab9","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x40"],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"MLOAD","error":""}
{"pc":26,"op":70,"gas":"0x278ab6","gasCost":"0x2","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x80"],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"CHAINID","error":""}
{"pc":27,"op":144,"gas":"0x278ab4","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x80","0x539"],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"SWAP1","error":""}
升级后evm的流程
{"pc":23,"op":96,"gas":"0x5c54030","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":[],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"PUSH1","error":""}
{"pc":25,"op":81,"gas":"0x5c5402d","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x40"],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"MLOAD","error":""}
{"pc":26,"op":70,"gas":"0x5c5402a","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x80"],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"CHAINID","error":"invalid opcode: CHAINID"}
{"pc":1507,"op":96,"gas":"0x17740a","gasCost":"0x3","memory":"","memSize":11904,"stack":["0xc9c65396","0x95","0x537e697c7ab75a26f9ecf0ce810e3154dfcaaf44","0x3a220f351252089d385b29beca14e27f204c296a","0x0","0x3a220f351252089d385b29beca14e27f204c296a","0x537e697c7ab75a26f9ecf0ce810e3154dfcaaf44","0x80","0xb81ff3c7140db37a05a85068ea614a23c0d72ec176588a2f45c5dd53550b6c88","0x0"],"returnStack":[],"returnData":null,"depth":2,"refund":0,"opName":"PUSH1","error":""}
从pc
指针可以看操作码70
后结果开始不一样了,70
对于的opcode
是CHAINID
,而CHAINID
是Istanbul
后加入的,由于IstanbulBlock
高度设置较高,功能为开启,所以跳转不一样。
trace log
也有报错"opName":"CHAINID","error":"invalid opcode: CHAINID"
。
两个问题都是YoloV1Block
未正确设置导致加载的EVMInterpreter
解析器不对导致
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!