开发环境
Browser-solidity是一个solidity在线网页开发ide
https://ethereum.github.io/browser-solidity
记得用chrome浏览器打开
相关操作如下
合约汇编分析
evm的变量存储是采用的方式存储的,核心是key的计算,对于不同类型的变量,这个key的计算方法也不一样
简单变量
contract C {
uint256 a = 2;
uint256 b = 3;
}
对应的汇编代码
PUSH1 0x2 PUSH1 0x0 SSTORE PUSH1 0x3 PUSH1 0x1 SSTORE
可见这种情况的 变量的key是变量的序号p(0, 1, 2, 3, ... )
这种情况下key = p
固定长度数组
contract D {
uint256[6] ar;
function D() {
ar[5] = 0x0ABCD;
}
}
PUSH2 0xABCD PUSH1 0x0 PUSH1 0x5 PUSH1 0x6 DUP2 LT ISZERO ISZERO PUSH1 0x6A JUMPI INVALID JUMPDEST ADD DUP2 SWAP1 SSTORE
PUSH2 0xABCD [0xABCD]
PUSH1 0x0 [0xABCD, 0x0]
PUSH1 0x5 [0xABCD, 0x0, 0x5]
PUSH1 0x6 [0xABCD, 0x0, 0x5, 0x6]
DUP2 LT ISZERO ISZERO PUSH1 0x6A [0xABCD, 0x0, 0x5, 0x0, 0x6A]
//判断是否超出数组范围
JUMPI INVALID [0xABCD, 0x0, 0x5]
JUMPDEST [0xABCD, 0x0, 0x5]
ADD [0xABCD, 0x5]
DUP2 [0xABCD, 0x5, 0xABCD]
SWAP1 [0xABCD, 0xABCD, 0x5]
SSTORE [0xABCD]
再增加一个赋值时
contract D {
uint256[6] ar;
function D() {
ar[4] = 0x02345;
ar[5] = 0x0ABCD;
}
}
对应的汇编为
PUSH2 0x2345 PUSH1 0x0 PUSH1 0x4 PUSH1 0x6 DUP2 LT ISZERO ISZERO PUSH1 0x6A JUMPI INVALID JUMPDEST ADD DUP2 SWAP1 SSTORE POP
PUSH2 0xABCD PUSH1 0x0 PUSH1 0x5 PUSH1 0x6 DUP2 LT ISZERO ISZERO PUSH1 0x81 JUMPI INVALID JUMPDEST ADD DUP2 SWAP1 SSTORE
可见这种情况下的key = p + index
如果我们的上面的index变为参数呢
contract D {
uint256[6] ar;
function D(uint256 index) {
ar[index] = 0x0789a;
}
}
PUSH2 0x789A PUSH1 0x0 DUP3 PUSH1 0x6 DUP2 LT ISZERO ISZERO PUSH1 0x7F JUMPI INVALID JUMPDEST ADD DUP2 SWAP1 SSTORE
可见index是DUP3可以得到,那这个DUP3对应的就是参数,那这个参数如何解释出来的呢?其实就是函数参数解释的问题
函数名检验
PUSH1 0x4 CALLDATASIZE LT PUSH1 0x3F JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0x6E9ED8CF EQ PUSH1 0x44 JUMPI
获取calldata的前四个字节和函数生成的hash前自己个字节(0x6E9ED8CF)比较,如果相同则为合法的调用
获取函数参数
然后继续获取真正的参数
POP PUSH1 0x6C PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH1 0x6E JUMP
上面是优化前的代码,下面是优化后的代码
PUSH1 0x58 PUSH1 0x4 CALLDATALOAD PUSH1 0x5A JUMP JUMPDEST STOP JUMPDEST
PUSH1 0x58 [0x58]
PUSH1 0x4 [0x58, 0x4]
CALLDATALOAD [0x58, arg]
PUSH1 0x5A []
JUMP JUMPDEST STOP JUMPDEST
PUSH1 0x6C [0x6C]
PUSH1 0x4 [0x6C, 0x4]
DUP1 [0x6C, 0x4, 0x4]
CALLDATASIZE [0x6C, 0x4, 0x4, 0x36]
SUB [0x6C, 0x4, 0x32]
DUP2 [0x6C, 0x4, 0x32, 0x4]
ADD [0x6C, 0x4, 0x36]
SWAP1 [0x6C, 0x36, 0x4]
DUP1 [0x6C, 0x36, 0x4, 0x4]
DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH1 0x6E JUMP
map实例
contract C {
mapping (uint => uint) map;
function C() {
map[1] = 2;
}
}
对应的汇编如下:
PUSH1 0x2 PUSH1 0x0 DUP1 PUSH1 0x1 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE
PUSH1 2 [0x2] //右侧[]为stack
PUSH1 0 [0x2, 0x0]
DUP1 [0x2, 0x0, 0x0]
PUSH1 1 [0x2, 0x0, 0x0, 0x01]
DUP2 [0x2, 0x0, 0x0, 0x01, 0x0]
MSTORE [0x2, 0x0, 0x0]
PUSH 20 [0x2, 0x0, 0x0, 0x20]
ADD [0x2, 0x0, 0x20]
SWAP1 [0x2, 0x20, 0x0]
DUP2 [0x2, 0x20, 0x0, 0x20]
MSTORE [0x2, 0x20]
PUSH1 20 [0x2, 0x20, 0x20]
ADD [0x2, 0x40]
PUSH1 0 [0x2, 0x40, 0x0]
KECCAK256 [0x2, sha3]
SWAP1 [sha3, 0x2]
SSTORE []
上面的代码中
map[1] = 2在stateDb是以形式保存,所以核心的逻辑是如何生成根据’map’变量和'1'这个key生成最后的newkey
newkey = KECCAK256(k . p)
这个例子中,k=1, p=0
KECCAK256工作流程
KECCAK256执行时需要两部分数据
- 输入数据 (memory)
- 存在memory中的输入数据的位置大小信息(stack)
准备memory:
Mem[0] = 0x1
PUSH1 2 [0x2]
PUSH1 0 [0x2, 0x0]
DUP1 [0x2, 0x0, 0x0]
PUSH1 1 [0x2, 0x0, 0x0, 0x01]
DUP2 [0x2, 0x0, 0x0, 0x01, 0x0]
MSTORE [0x2, 0x0, 0x0]
Mem[0x20] = 0x0
PUSH1 20 [0x2, 0x0, 0x0, 0x20]
ADD [0x2, 0x0, 0x20]
SWAP1 [0x2, 0x20, 0x0]
DUP2 [0x2, 0x20, 0x0, 0x20]
MSTORE [0x2, 0x20]
准备stack:
PUSH1 20 [0x2, 0x20, 0x20]
ADD [0x2, 0x40]
PUSH1 0 [0x2, 0x40, 0x0]
生成hash
赋值
SWAP1 [sha3, 0x2]
SSTORE []
/********************************
* 本文来自CSDN博主"爱踢门"
* 转载请标明出处:http://blog.csdn.net/itleaks
******************************************/