参考[link](https://blog.csdn.net/weixin_43563956/article/details/127725385
大致流程如下:
编写合约 > 生成abi > 解析abi得出指令集 > 指令通过opcode来映射成操作码集 > 生成一个operation
以太坊虚拟机的工作流程:
由solidity语言编写的智能合约,通过编译器编译成bytecode,之后发到以太坊上,以太坊底层通过evm模块支持合约的执行和调用,调用时根据合约获取代码,即合约的字节码,生成环境后载入到 EVM 执行。
1、操作码opcodes.go
合约编译出来的bytecode中,一个OpCode就是上面的一位。opcodes按功能分为9组,以第一位十六进制数来分类,例如0x1x,0x2x。
opCodeRange | 对应操作 |
---|---|
0x0 | arithmetic ops算数操作 |
0x10 | comparison ops比较操作 |
0x20 | crypto加密操作 |
0x30 | closure state状态闭包 |
0x40 | block operations区块操作 |
0x50 | ‘storage’ and execution存储和执行操作 |
0x60 | pushes压栈操作 |
0x80 | dups克隆操作 |
0x90 | swaps交换操作 |
0xa0 | logging ops日志操作 |
0xf0 | closures闭包 |
2、合约contract.go
NewContract函数构造了新的合约,且如果是被合约调用,则复用该合约的 jumpdests。
validJumpdest
函数用于验证给定的目标地址是否为有效的跳转目标。通过获取目标地址对应的操作码,判断是否为JUMPDEST
类型。如果不是,则返回false
,表示无效的跳转目标。调用c.isCode(udest)
方法来进一步验证目标地址是否为有效的代码位置。如果是有效的代码位置,则返回true
,表示有效的跳转目标;否则返回false
。
func (c *Contract) validJumpdest(dest *uint256.Int) bool {
udest, overflow := dest.Uint64WithOverflow()
// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
// Don't bother checking for JUMPDEST in that case.
if overflow || udest >= uint64(len(c.Code)) {
return false
}
// Only JUMPDESTs allowed for destinations
if OpCode(c.Code[udest]) != JUMPDEST {
return false
}
return c.isCode(udest)
}
isCode
函数判断给定的地址是否为有效的代码段。
3、jump_table.go
这是跳转表。在不同的以太坊版本中,会填充不一样的字段。对指令的真正的解释函数是在这个部分里面,而不是在解释器当中。
其中frontierInstructionSet
这个对象包含了最基本的指令信息,其它是对这个集合的扩充,最全的一个是 constantinopleInstructionSet
。
operation使用的时候以指令的opcode值为索引。其中包括指令的解释执行函数、要消耗的gas值、栈空间大小和消耗的内存空间大小函数(在memory.go中实现)。
type operation struct {
// execute is the operation function
execute executionFunc
constantGas uint64
dynamicGas gasFunc
// minStack tells how many stack items are required
minStack int
// maxStack specifies the max length the stack can have for this operation
// to not overflow the stack.
maxStack int
// memorySize returns the memory size required for the operation
memorySize memorySizeFunc
}
针对不同的jump-table有不同的函数,里面有不同的解释执行函数。
在每一种类型中,实现不同的operation对象。
而validate
函数用来检查jump_table中的操作是否为空。
func validate(jt JumpTable) JumpTable {
for i, op := range jt {
if op == nil {
panic(fmt.Sprintf("op %#x is not set", i))
}
if op.memorySize != nil && op.dynamicGas == nil {
panic(fmt.Sprintf("op %v has dynamic memory but not dynamic gas", OpCode(i).String()))
}
}
return jt
}
4、其它文件
gas.go和gas_table.go
这两个文件是用来计算所消耗的gas值,在具体的gas_table.go文件中,针对不同的操作有不同的函数来进行不同的计算。例如
contracts,go文件用于存放预编译好的合约
common.go用于存放一些常用的工具方法