EVMbatch:论手拆EVM opcode
万事开头难
起因:希望泛化DownTop项目,实现地址库自定义,成品点这
项目实现(走个流程)
查阅资料可知,solc编译出的input data基本分为以下三部分:
0x${constructorCode}${runtimeCode}fe${dataSegment}
-功能上:constructorCode主要负责将后两部分压入内存。runtimeCode和dataSegment和它们的名称一样,分别负责逻辑与数据储存,它们的长度会影响constructor内部CODECOPY->size的值。
-所以要想实现地址库自定义,需要做的事很简单,就是根据data动态算出codesize&各个for循环的循环次数,然后再写回字节码
拆完以后能看到这样的结构:
constructCode:601461${runtimeSizeHex}806100155f393360601b8152015ff3fe
runtimeCode:6014.601461${dataSizeHex}04.601461${dataSizeHex}04.73${_tokenAddress}.fe
-此时计算并写入runtimeSizeHex与dataSizeHex以及_tokenAddress就能实现我们最初的目的
一些有趣的发现(这才是想写的)
1.YUL编译器并不会对显然是常量的参数进行优化(不确定是否个例)
-在runtimeCode中,601461${dataSizeHex}04实际上为dataSizeHex/0x14,0x14指地址字节数为20,这实际上是个可以被确定的常量,这样编译至少会浪费3+3+5(PUSH1+PUSH2+DIV)的gas
2.PUSH1与PUSH2的差别
-PUSH1最大可表示0xff,PUSH2则为0xffff
-{dataSizeHex}在最初的实验中使用了PUSH1推入,但后实际测试发现这种写法只能兼容12个地址以下的情况,于是使用PUSH2,理论上支持超过eip-170限制的addresses数量
-有趣的是,PUSH1与PUSH2消耗的gas量是一样的
3.constructor内的妙妙操作
-上文所言的三段式结构只是相对的,实际上我们可以在constructor函数里夹点私货,比如说加个鉴权功能,比如说下列opcode:
39 33 60 1b 81 52 01 5f f3 fe
-它读取了CALLER(33), 并将它压入了内存末尾,方便runtime鉴权msg.sender