以太坊EVM原理与实现
太坊底层通过EVM模块支持合约的执行与调用,调用时根据合约地址获取到代码,生成环境后载入到EVM中运行。通常智能合约的开发流程是用solidlity编写逻辑代码,再通过编译器编译元数据,最后再发布到以太坊上。
代码结构
.
├── analysis.go //跳转目标判定
├── common.go
├── contract.go //合约数据结构
├── contracts.go //预编译好的合约
├── errors.go
├── evm.go //执行器 对外提供一些外部接口
├── gas.go //call gas花费计算 一级指令耗费gas级别
├── gas_table.go //指令耗费计算函数表
├── gen_structlog.go
├── instructions.go //指令操作
├── interface.go
├── interpreter.go //解释器 调用核心
├── intpool.go //int值池
├── int_pool_verifier_empty.go
├── int_pool_verifier.go
├── jump_table.go //指令和指令操作(操作,花费,验证)对应表
├── logger.go //状态日志
├── memory.go //EVM 内存
├── memory_table.go //EVM 内存操作表 主要衡量操作所需内存大小
├── noop.go
├── opcodes.go //Op指令 以及一些对应关系
├── runtime
│ ├── env.go //执行环境
│ ├── fuzz.go
│ └── runtime.go //运行接口 测试使用
├── stack.go //栈
└── stack_table.go //栈验证
指令 OpCode
opcodes.go中定义了所有的OpCode,该值是一个byte,合约编译出来的bytecode中,一个OpCode就是上面的一位。opcodes按功能分为9组(运算相关,块操作,加密相关等)。
// 0x0 range - arithmetic ops.
const (...)
// 0x10 range - comparison ops.
const (...)
// 0x30 range - closure state.
const (...)
// 0x40 range - block operations.
const (
BLOCKHASH OpCode = 0x40 + iota
COINBASE
TIMESTAMP
NUMBER
DIFFICULTY
GASLIMIT
)
// 0x50 range - 'storage' and execution.
const (...)
// 0x60 range.
const (...)
// 0xa0 range - logging ops.
const (...)
// unofficial opcodes used for parsing.
const (...)
// 0xf0 range - closures.
const (...)
instruction
jump.table.go定义了四种指令集合,每个集合实质上是个256长度的数组,名字翻译过来是(荒地,农庄,拜占庭,君士坦丁堡)对应了EVM的四个发展阶段。指令集向前兼容。
var (
frontierInstructionSet = newFrontierInstructionSet()
homesteadInstructionSet = newHomesteadInstructionSet()
byzantiumInstructionSet = newByzantiumInstructionSet()
constantinopleInstructionSet = newConstantinopleInstructionSet()
)
具体每条指令结构如下,字段意思见注释
type operation struct {
// execute is the operation function
execute executionFunc//执行函数
// gasCost is the gas function and returns the gas required for execution
gasCost gasFunc//gas消耗函数
// validateStack validates the stack (size) for the operation
validateStack stackValidationFunc//堆栈大小验证函数
// memorySize returns the memory size required for the operation
memorySize memorySizeFunc //需要的内存大小
halts bool // 表示操作是否停止进一步执行 运算终止 indicates whether the operation should halt further execution
jumps bool // 指示程序计数器是否不增加 跳转(for) indicates whether the program counter should not increment
writes bool // 确定这是否是一个状态修改操作 是否写入determines whether this a state modifying operation
valid bool // 指示检索到的操作是否有效并且已知 操作是否有效 indication whether the retrieved operation is valid and known
reverts bool // 确定操作是否恢复状态(隐式停止) 出错回滚determines whether the operation reverts state (implicitly halts)
returns bool // 确定操作是否设置了返回数据内容 返回determines whether the operations sets the return data content
}
按下面的sha3指令为例
定义
SHA3: {
execute: opSha3,
gasCost: gasSha3,
validateStack: makeStackFunc(2, 1),
memorySize: memorySha3,
valid: true,
},
操作
不同的操作有所不同,操作对象根据指令不同可能影响栈、内存、statedb。
func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
//从堆栈中获取内存的地址(offset+size)从内存中取出来数据
offset, size := stack.pop(), stack.pop()
data := memory.Get(offset.Int64(), size.Int64())
//keccak256处理
if interpreter.hasher == nil {
interpreter.hasher = sha3.NewLegacyKeccak256().(keccakState)
} else {
interpreter.hasher.Reset()
}
interpreter.hasher.Write(data)
interpreter.hasher.Read(interpreter.hasherBuf[:])
evm := interpreter.evm
if evm.vmConfig.EnablePreimageRecording {
//把(hash,data)当作preimage写到数据库中
evm.StateDB.AddPreimage(interpreter.hasherBuf, data)
}
//hash入栈
stack.push(interpreter.intPool.get().SetBytes(interpreter.hasherBuf[:]))
interpreter.intPool.put(offset, size)
return nil, nil
}
gas费用
不同的操作有不同的初始值和对应的计算方法,具体的方法都定义在gas_table里面,按sha3为例
func gasSha3(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var overflow bool
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
//+30 Once per SHA3 operation.
if gas, overflow = math.SafeAdd(gas, params.Sha3Gas); overflow {
return 0, errGasUintOverflow
}
wordGas, overflow := bigUint64(stack.Back(1))
if overflow {
return 0, errGasUintOverflow
}
//*6 Once per word of the SHA3 operation's data.
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
return 0, errGasUintOverflow
}
//gas + wordGas
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
return 0, errGasUintOverflow
}
return gas, nil
}
有两个定义会影响gas的计算,通常作为量化的一个单位。
//github.com/ethereum/go-ethereum/core/vm/gas.go
// Gas costs
const (
GasQuickStep uint64 = 2
GasFastestStep uint64 = 3
GasFastStep uint64 = 5
GasMidStep uint64 = 8
GasSlowStep uint64 = 10
GasExtStep uint64 = 20
GasReturn uint64 = 0
GasStop uint64 = 0
GasContractByte uint64 = 200
)
//github.com/ethereum/go-ethereum/params/gas_table.go
// GasTable organizes gas prices for different ethereum phases.
type GasTable struct {
ExtcodeSize uint64
ExtcodeCopy uint64
ExtcodeHash uint64
Balance uint64
SLoad uint64
Calls uint64
Suicide uint64
ExpByte uint64
// CreateBySuicide occurs when the
// refunded account is one that does
// not exist. This logic is similar
// to call. May be left nil. Nil means
// not charged.
CreateBySuicide uint64
}
memorysize
sha3根据栈顶的两个数据,计算memorysize。有些操作不需要申请内存因而默认为0
func memorySha3(stack *Stack) *big.Int {
return calcMemSize(stack.Back(0), stack.Back(1))
}
栈验证
先验证栈上的操作数够不够,在验证栈是否超出最大限制,sha3在这里仅需要验证其参数够不够,运算之后栈是要减一的makeStackFunc(2, 1)
func makeStackFunc(pop, push int) stackValidationFunc {
return func(stack *Stack) error {
//深度验证
if err := stack.require(pop); err != nil {
return err
}
//最大值验证 StackLimit = 1024
if stack.len()+push-pop > int(params.StackLimit) {
return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit)
}
return nil
}
}
智能合约
合约是EVM智能合约的存储单位也是解释器执行的基本单位,包含了代码,调用人,所有人,gas的相关信息。
// EVM是以太坊虚拟机基础对象,并提供必要的工具,以使用提供的上下文运行给定状态的合约。
// 应该指出的是,任何调用产生的任何错误都应该被认为是一种回滚修改状态和消耗所有GAS操作,
// 不应该执行对具体错误的检查。 解释器确保生成的任何错误都被认为是错误的代码。
type EVM struct {
Context // Context provides auxiliary blockchain related information
StateDB StateDB // StateDB gives access to the underlying state
depth int //当前的调用堆栈
// chainConfig contains information about the current chain
chainConfig *params.ChainConfig
// chain rules contains the chain rules for the current epoch
chainRules params.Rules
// virtual machine configuration options used to initialise the
// evm.
vmConfig Config
// global (to this context) ethereum virtual machine
// used throughout the execution of the tx.
interpreters []Interpreter
interpreter Interpreter
// abort is used to abort the EVM calling operations
// NOTE: must be set atomically
abort int32
// callGasTemp holds the gas available for the current call. This is needed because the
// available gas is calculated in gasCall* according to the 63/64 rule and later
// applied in opCall*.
callGasTemp uint64
}
EVM原生编译了一批合约,定义在contracts.go里面,主要用于加密操作。
// PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum
// contracts used in the Byzantium release.
var PrecompiledContractsByzantium = 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}): &bn256Add{},
common.BytesToAddress([]byte{7}): &bn256ScalarMul{},
common.BytesToAddress([]byte{8}): &bn256Pairing{},
}
执行机
栈
EVM中栈用于保存操作数,每个操作数的类型是big.int,故说EVM是256位虚拟机。执行opcode的时候,从上往下弹出操作数,作为操作的参数。
// Stack is an object for basic stack operations. Items popped to the stack are
// expected to be changed and modified. stack does not take care of adding newly
// initialised objects.
type Stack struct {
data []*big.Int
}
//追加到末尾
func (st *Stack) push(d *big.Int) {
// NOTE push limit (1024) is checked in baseCheck
//stackItem := new(big.Int).Set(d)
//st.data = append(st.data, stackItem)
st.data = append(st.data, d)
}
//从最末尾取出
func (st *Stack) pop() (ret *big.Int) {
ret = st.data[len(st.data)-1]
st.data = st.data[:len(st.data)-1]
return
}
//交换栈顶元素和里栈顶n距离的元素的值
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]
}
//相复制指定位置的值到栈顶
func (st *Stack) dup(pool *intPool, n int) {
st.push(pool.get().Set(st.data[st.len()-n]))
}
//取栈顶的元素
func (st *Stack) peek() *big.Int {
return st.data[st.len()-1]
}
内存
内存用于一些内存操作(MLOAD,MSTORE,MSTORE8)及合约调用的参数拷贝(CALL,CALLCODE)。
内存数据结构,维护了一个byte数组,MLOAD,MSTORE读取存入的时候都要指定位置及长度才能准确的读写。
// Memory implements a simple memory model for the ethereum virtual machine.
type Memory struct {
store []byte
lastGasCost uint64
}
// Set sets offset + size to value
func (m *Memory) Set(offset, size uint64, value []byte) {
// It's possible the offset is greater than 0 and size equals 0. This is because
// the calcMemSize (common.go) could potentially return 0 when size is zero (NO-OP)
if size > 0 {
// length of store may never be less than offset + size.
// The store should be resized PRIOR to setting the memory
if offset+size > uint64(len(m.store)) {
panic("invalid memory: store empty")
}
copy(m.store[offset:offset+size], value)
}
}
// Get returns offset + size as a new slice
func (m *Memory) Get(offset, size int64) (cpy []byte) {
if size == 0 {
return nil
}
if len(m.store) > int(offset) {
cpy = make([]byte, size)
copy(cpy, m.store[offset:offset+size])
return
}
return
}
内存操作指令详见 EVM7种重要指令实现原理 一文
stateDb
合约本身不保存数据,那么合约的数据是保存在哪里呢?合约及其调用类似于数据库的日志,保存了合约定义以及对他的一系列操作,只要将这些操作执行一遍就能获取当前的结果,但是如果每次都要去执行就太慢了,因而这部分数据是会持久化到stateDb里面的。code中定义了两条指令SSTORE SLOAD用于从db中读写合约当前的状态。详见 EVM7种重要指令实现原理 一文
执行过程
执行入口定义在evm.go中,功能就是组装执行环境(代码,执行人关系,参数等)
//Call 执行于给定的input作为参数与addr相关联的合约
//他还处理所需的任何必要的转账操作,并采取必要的步骤来创建账户
//并在任意错误的情况下回滚所做的操作
//Call方法, 无论我们转账或者是执行合约代码都会调用到这里, 同时合约里面的call指令也会执行到这里
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { //调用深度最多1024
return nil, gas, ErrDepth
}
// Fail if we're trying to transfer more than the available balance
//查看我们的账户是否有足够的金钱
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance
}
var (
to = AccountRef(addr)
snapshot = evm.StateDB.Snapshot()
)
if !evm.StateDB.Exist(addr) { //查看指定地址是否存在
//如果地址不存在,查看是否是 native go 的合约, native go 的合约在 contracts.go 文件里面
precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
precompiles = PrecompiledContractsByzantium
}
if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
//如果不是指定的合约地址,并且value的值为0那么返回正常,而且这次调用没有消耗Gas
// Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
}
return nil, gas, nil
}
//负责在本地状态创建addr
evm.StateDB.CreateAccount(addr)
}
//执行转账
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, to, value, gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
// Even if the account has no code, we need to continue because it might be a precompile
start := time.Now()
// Capture the tracer start/end events in debug mode
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
defer func() { // Lazy evaluation of the parameters
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
}()
}
ret, err = run(evm, contract, input, false)
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in homestead this also counts for code storage gas errors.
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted {
// 如果是由revert指令触发的错误,因为ICO一般设置了人数限制或者资金限制
// 在大家抢购的时候很可能会触发这些限制条件,导致被抽走不少钱。这个时候
// 又不能设置比较低的GasPrice和GasLimit。因为要速度快。
// 那么不会使用剩下的全部Gas,而是只会使用代码执行的Gas
// 不然会被抽走 GasLimit *GasPrice的钱,那可不少。
contract.UseGas(contract.Gas)
}
}
return ret, contract.Gas, err
}
类似的函数有四个。详细区别见最后的参考。
- Call A->B A,B的环境独立
- CallCode、 和Call类似,与Call不同的地方在与它使用caller的context来执行给定地址的代码
- DelegateCall、 和CallCode类似,区别在于msg.send不一样 caller被设置为caller的caller
- StaticCall 和call相似 不允许执行任何修改状态的操作
Contract和参数构造完成后调用执行函数,执行函数会检查调用的是否会之前编译好的原生合约,如果是原生合约则调用原生合约,否则调用解释器执行函数运算合约。
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
if contract.CodeAddr != nil {
precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
precompiles = PrecompiledContractsByzantium
}
if p := precompiles[*contract.CodeAddr]; p != nil {
return RunPrecompiledContract(p, input, contract)
}
}
for _, interpreter := range evm.interpreters {
if interpreter.CanRun(contract.Code) {
if evm.interpreter != interpreter {
// Ensure that the interpreter pointer is set back
// to its current value upon return.
defer func(i Interpreter) {
evm.interpreter = I
}(evm.interpreter)
evm.interpreter = interpreter
}
return interpreter.Run(contract, input, readOnly)
}
}
return nil, ErrNoCompatibleInterpreter
}
解释器
// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
//
// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// errExecutionReverted which means revert-and-keep-gas-left.
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
if in.intPool == nil {
in.intPool = poolOfIntPools.get()
defer func() {
poolOfIntPools.put(in.intPool)
in.intPool = nil
}()
}
// Increment the call depth which is restricted to 1024
in.evm.depth++
defer func() { in.evm.depth-- }()
// Make sure the readOnly is only set if we aren't in readOnly yet.
// This makes also sure that the readOnly flag isn't removed for child calls.
if readOnly && !in.readOnly {
in.readOnly = true
defer func() { in.readOnly = false }()
}
// Reset the previous call's return data. It's unimportant to preserve the old buffer
// as every returning call will return new data anyway.
in.returnData = nil
// Don't bother with the execution if there's no code.
if len(contract.Code) == 0 {
return nil, nil
}
var (
op OpCode // current opcode 当前指令
mem = NewMemory() // bound memory 内存
stack = newstack() // local stack 栈
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC
// to be uint256. Practically much less so feasible.
pc = uint64(0) // program counter 指令位置
cost uint64 //gas 花费
// copies used by tracer
pcCopy uint64 // needed for the deferred Tracer debug使用
gasCopy uint64 // for Tracer to log gas remaining before execution debug使用
logged bool // deferred Tracer should ignore already logged steps debug使用
)
contract.Input = input
// Reclaim the stack as an int pool when the execution stops
defer func() { in.intPool.put(stack.data...) }()
//解释器的主要循环,直到遇到STOP,RETURN,SELFDESTRUCT指令被执行,或者是遇到任意错误,或者说done被父context设置。
for atomic.LoadInt32(&in.evm.abort) == 0 {
// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
op = contract.GetOp(pc)//获取一条指令及指令对应的操作
operation := in.cfg.JumpTable[op]
if !operation.valid {
return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
}
if err := operation.validateStack(stack); err != nil {
return nil, err
}
// If the operation is valid, enforce and write restrictions
if err := in.enforceRestrictions(op, operation, stack); err != nil {
return nil, err
}
var memorySize uint64
// calculate the new memory size and expand the memory to fit
// the operation
//计算内存,按操作所需要的操作数来算
if operation.memorySize != nil {
memSize, overflow := bigUint64(operation.memorySize(stack))
if overflow {
return nil, errGasUintOverflow
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, errGasUintOverflow
}
}
// consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
//校验cost 调用前面提到的constfunc 计算本次操作cost消耗
cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize)
if err != nil || !contract.UseGas(cost) {
return nil, ErrOutOfGas
}
if memorySize > 0 {//扩大内存范围
mem.Resize(memorySize)
}
// execute the operation
//执行操作
res, err := operation.execute(&pc, in, contract, mem, stack)
// if the operation clears the return data (e.g. it has returning data)
// set the last return to the result of the operation.
//如果遇到return 设置返回值
if operation.returns {
in.returnData = res
}
switch {
case err != nil:
return nil, err //报错
case operation.reverts:
return res, errExecutionReverted //出错回滚
case operation.halts:
return res, nil //停止
case !operation.jumps: //跳转
pc++
}
}
return nil, nil
}
我们直接看解析器处理的主循环,之前的代码都是在初始化一些临时变量。
1,首先调用contract.GetOp(pc)从和约二进制数据里取得第pc个opcode,opcode是以太坊虚拟机指令,一共不超过256个,正好一个byte大小能装下。
2,从解析器的JumpTable表中查到op对应的operation。比如opcode是SHA3(0x20),取到的op就是上面呢举例的operation。
3,如果operation可用,解析器栈不超过1024,且读写不冲突
4,计算operation的memorysize,不能大于64位。
5,根据不同的指令,指令的memorysize等,调用operation.gasCost()方法计算执行operation指令需要消耗的gas。
6,调用operation.execute(&pc, in.evm, contract, mem, stack)执行指令对应的方法。
7,operation.reverts值是true或者operation.halts值是true的指令,会跳出主循环,否则继续遍历下个op。
8,operation指令集里面有4个特殊的指令LOG0,LOG1,LOG2,LOG3,它们的指令执行方法makeLog()会产生日志数据,日志内容包括EVM解析栈内容,指令内存数据,区块信息,合约信息等。这些日志数据会写入到tx的Receipt的logs里面,并存入本地ldb数据库。
Solidity案例
和其他语言类似,有了字节码运行机,就可以在字节码上面再组织其他高级语言,而solidlity语言就是实现了这样的语言编译器,方便了合约编写,有利于推广以太坊dapp开发。
pragma solidity >=0.4.21 <0.6.0;
contract simple {
uint num =0;
constructor() public{
num =1;
}
function add(uint i ) public returns(uint){
uint m =99;
num =num*i +m;
return num;
}
}
生成的Opcodes码
使用remix编译上述合约,得到bytecode
JUMPDEST 函数入口
PUSH + JUMPI/JUMP 类似于调用函数
CALLDATASIZE + CALLDATALOAD 大约是获取函数参数
.code
PUSH 80 contract simple {\n uint nu...
PUSH 40 contract simple {\n uint nu...
MSTORE contract simple {\n uint nu...
PUSH 0 0
DUP1 uint num =0
SSTORE uint num =0
CALLVALUE constructor() public{\n ...
DUP1 olidity >
ISZERO a
PUSH [tag] 1 a
JUMPI a
PUSH 0 0
DUP1 .
REVERT 4.21 <0.6.0;
tag 1 a
JUMPDEST a
POP constructor() public{\n ...
PUSH 1 1
PUSH 0 num
DUP2 num =1
SWAP1 num =1
SSTORE num =1
POP num =1
PUSH #[$] 0000000000000000000000000000000000000000000000000000000000000000 contract simple {\n uint nu...
DUP1 contract simple {\n uint nu...
PUSH [$] 0000000000000000000000000000000000000000000000000000000000000000 contract simple {\n uint nu...
PUSH 0 contract simple {\n uint nu...
CODECOPY contract simple {\n uint nu...
PUSH 0 contract simple {\n uint nu...
RETURN contract simple {\n uint nu...
.data
0:
.code
PUSH 80 contract simple {\n uint nu...
PUSH 40 contract simple {\n uint nu...
MSTORE contract simple {\n uint nu...
PUSH 4 contract simple {\n uint nu...
CALLDATASIZE contract simple {\n uint nu...
LT contract simple {\n uint nu...
PUSH [tag] 1 contract simple {\n uint nu...
JUMPI contract simple {\n uint nu...
PUSH 0 contract simple {\n uint nu...
CALLDATALOAD contract simple {\n uint nu...
PUSH 100000000000000000000000000000000000000000000000000000000 contract simple {\n uint nu...
SWAP1 contract simple {\n uint nu...
DIV contract simple {\n uint nu...
PUSH FFFFFFFF contract simple {\n uint nu...
AND contract simple {\n uint nu...
DUP1 contract simple {\n uint nu...
PUSH 1003E2D2 contract simple {\n uint nu...
EQ contract simple {\n uint nu...
PUSH [tag] 2 contract simple {\n uint nu...
JUMPI contract simple {\n uint nu...
tag 1 contract simple {\n uint nu...
JUMPDEST contract simple {\n uint nu...
PUSH 0 contract simple {\n uint nu...
DUP1 contract simple {\n uint nu...
REVERT contract simple {\n uint nu...
tag 2 function add(uint i ) public r...
JUMPDEST function add(uint i ) public r...
CALLVALUE function add(uint i ) public r...
DUP1 olidity >
ISZERO a
PUSH [tag] 3 a
JUMPI a
PUSH 0 0
DUP1 .
REVERT 4.21 <0.6.0;
tag 3 a
JUMPDEST a
POP function add(uint i ) public r...
PUSH [tag] 4 function add(uint i ) public r...
PUSH 4 function add(uint i ) public r...
DUP1 function add(uint i ) public r...
CALLDATASIZE function add(uint i ) public r...
SUB function add(uint i ) public r...
DUP2 function add(uint i ) public r...
ADD function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
DUP1 function add(uint i ) public r...
DUP1 function add(uint i ) public r...
CALLDATALOAD function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
PUSH 20 function add(uint i ) public r...
ADD function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
SWAP3 function add(uint i ) public r...
SWAP2 function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
POP function add(uint i ) public r...
POP function add(uint i ) public r...
POP function add(uint i ) public r...
PUSH [tag] 5 function add(uint i ) public r...
JUMP function add(uint i ) public r...
tag 4 function add(uint i ) public r...
JUMPDEST function add(uint i ) public r...
PUSH 40 function add(uint i ) public r...
MLOAD function add(uint i ) public r...
DUP1 function add(uint i ) public r...
DUP3 function add(uint i ) public r...
DUP2 function add(uint i ) public r...
MSTORE function add(uint i ) public r...
PUSH 20 function add(uint i ) public r...
ADD function add(uint i ) public r...
SWAP2 function add(uint i ) public r...
POP function add(uint i ) public r...
POP function add(uint i ) public r...
PUSH 40 function add(uint i ) public r...
MLOAD function add(uint i ) public r...
DUP1 function add(uint i ) public r...
SWAP2 function add(uint i ) public r...
SUB function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
RETURN function add(uint i ) public r...
tag 5 function add(uint i ) public r...
JUMPDEST function add(uint i ) public r...
PUSH 0 uint
DUP1 uint m
PUSH 63 99
SWAP1 uint m =99
POP uint m =99
DUP1 m
DUP4 I
PUSH 0 num
SLOAD num
MUL num*I
ADD num*I +m
PUSH 0 num
DUP2 num =num*I +m
SWAP1 num =num*I +m
SSTORE num =num*I +m
POP num =num*I +m
PUSH 0 num
SLOAD num
SWAP2 return num
POP return num
POP function add(uint i ) public r...
SWAP2 function add(uint i ) public r...
SWAP1 function add(uint i ) public r...
POP function add(uint i ) public r...
JUMP [out] function add(uint i ) public r...
.data