Evm7种重要指令的实现原理:
Evm的所有指令定义都在core/vm/jump_table.go里实现的,而每个指令对应的操作函数都是在core/vm/instructions.go里实现的。
如果一个节点并发调用智能合约,那么对memory的操作是否有线程安全问题。不会,因为每执行一个交易,都会创建一个新的evm对象。只有最终写入statedb的数据会有线程安全问题。
基本原理:一个指令占一个字节,也就是8为,16进制从0x01到0xff,10进制从1到255。最多255个指令,如果加上0的话,就256个指令。evm执行指令的主要载体是栈。
栈里的dup函数实现把栈上的某个值存入intpool中。
mload:花费32gas*数据大小,用途:取出栈顶的元素作为key,从memory中取出该key对应的value,存入initpoll中最新元素,并且把该值压入栈中。
Mstore:取出栈上的最新的两个数据,一个作为key,一个作为value,写入memory,并且存入initpoll中。initpoll也是一个栈的结构
func opMload(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
offset := stack.pop()
val := interpreter.intPool.get().SetBytes(memory.Get(offset.Int64(), 32))
stack.push(val)
interpreter.intPool.put(offset)
return nil, nil
}
func opMstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// pop value of the stack
mStart, val := stack.pop(), stack.pop()
memory.Set32(mStart.Uint64(), val)
interpreter.intPool.put(mStart, val)
return nil, nil
}
// intPool is a pool of big integers that
// can be reused for all big.Int operations.
type intPool struct {
pool *Stack
}
sload:从statedb中取出合约地址下面的某个key对应的value值,存入栈的最新元素里。sload固定是200gas
Sstore:从栈中取中两个值作为key和value,然后在statedb中存入刚才取出的key和value,并且在initpool中放入该value。
func opSload(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
loc := stack.peek()
val := interpreter.evm.StateDB.GetState(contract.Address(), common.BigToHash(loc))
loc.SetBytes(val.Bytes())
return nil, nil
}
func opSstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
loc := common.BigToHash(stack.pop())
val := stack.pop()
interpreter.evm.StateDB.SetState(contract.Address(), loc, common.BigToHash(val))
interpreter.intPool.put(val)
return nil, nil
}
实现的功能是:从字节指令代码数据中取出从pc计数器到x个指令出来,压入栈中。x为1到32。
// make push instruction function
func makePush(size uint64, pushByteSize int) executionFunc {
return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
codeLen := len(contract.Code)
startMin := codeLen
if int(*pc+1) < startMin {
startMin = int(*pc + 1)
}
endMin := codeLen
if startMin+pushByteSize < endMin {
endMin = startMin + pushByteSize
}
integer := interpreter.intPool.get()
stack.push(integer.SetBytes(common.RightPadBytes(contract.Code[startMin:endMin], pushByteSize)))
*pc += size
return nil, nil
}
}
dump是转存的意思,主要实现的是把栈中的某个元素压入栈顶
具体实现的功能是:从栈顶开始算起,把栈上第x个元素存入intpool的栈顶,并且把该元素也存入栈的栈顶。
// make dup instruction function
func makeDup(size int64) executionFunc {
return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.dup(interpreter.intPool, int(size))
return nil, nil
}
}
func (st *Stack) dup(pool *intPool, n int) {
st.push(pool.get().Set(st.data[st.len()-n]))
}
实现的功能是:把栈上的第x个元素和栈顶元素进行交换
// make swap instruction function
func makeSwap(size int64) executionFunc {
// switch n + 1 otherwise n would be swapped with n
size++
return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.swap(int(size))
return nil, nil
}
}
func (st *Stack) swap(n int) {
st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n]
}
实现的功能是:根据栈的前两个元素作为key和size,从内存里取出相应的数据,存入statedb的journal(日志)里。1,2,3,4代表的是日志的主题,一次最多可以存入4个主题。
如果有多个主题,取数据的时候从data里切分出不同主题的数据。
// make log instruction function
func makeLog(size int) executionFunc {
return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
topics := make([]common.Hash, size)
mStart, mSize := stack.pop(), stack.pop()
for i := 0; i < size; i++ {
topics[i] = common.BigToHash(stack.pop())
}
d := memory.Get(mStart.Int64(), mSize.Int64())
interpreter.evm.StateDB.AddLog(&types.Log{
Address: contract.Address(),
Topics: topics,
Data: d,
// This is a non-consensus field, but assigned here because
// core/state doesn't know the current block number.
BlockNumber: interpreter.evm.BlockNumber.Uint64(),
})
interpreter.intPool.put(mStart, mSize)
return nil, nil
}
}
func (self *StateDB) AddLog(log *types.Log) {
self.journal.append(addLogChange{txhash: self.thash})
log.TxHash = self.thash
log.BlockHash = self.bhash
log.TxIndex = uint(self.txIndex)
log.Index = self.logSize
self.logs[self.thash] = append(self.logs[self.thash], log)
self.logSize++
}
(1) Create指令实现的是创建合约,将会调用
res, addr, returnGas, suberr := interpreter.evm.Create(contract, input, gas, value)
(2)call指令实现的是调用合约,将会调用
ret, returnGas, err := interpreter.evm.Call(contract, toAddr, args, gas, value)
(3)callcode指令实现的是一个合约调用其他合约,最终将会调用callcode方法,和call方法最大不同的是执行合约的上下文是调用者,而不是将要执行的合约。
ret, returnGas, err := interpreter.evm.CallCode(contract, toAddr, args, gas, value)
callcode一般发生在定义了多个合约,其中一个合约调用了其他合约的方法。
(4)return指令实现的是:从内存中,以栈顶的前两个元素作为偏移量和size,取出相应的数据放入intpool中,并返回数据
func opReturn(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
offset, size := stack.pop(), stack.pop()
ret := memory.GetPtr(offset.Int64(), size.Int64())
interpreter.intPool.put(offset, size)
return ret, nil
}