verkle: Implement Trie, NodeIterator and Database ifs Fix crash in TestDump Fix TestDump Fix TrieCopy remove unnecessary traces fix: Error() returned errIteratorEnd in verkle node iterator rewrite the iterator and change the signature of OpenStorageTrie add the adapter to reuse the account trie for storage don't try to deserialize a storage leaf into an account Fix statedb unit tests (#14) * debug code * Fix more unit tests * remove traces * Go back to the full range One tree to rule them all remove updateRoot, there is no root to update store code inside the account leaf fix build save current state for Sina Update go-verkle to latest Charge WITNESS_*_COST gas on storage loads Add witness costs for SSTORE as well Charge witness gas in the case of code execution corresponding code deletion add a --verkle flag to separate verkle experiments from regular geth operations use the snapshot to get data stateless execution from block witness AccessWitness functions Add block generation test + genesis snapshot generation test stateless block execution (#18) * test stateless block execution * Force tree resolution before generating the proof increased coverage in stateless test execution (#19) * test stateless block execution * Force tree resolution before generating the proof * increase coverage in stateless test execution ensure geth compiles fix issues in tests with verkle trees deactivated Ensure stateless data is available when executing statelessly (#20) * Ensure stateless data is available when executing statelessly * Actual execution of a statless block * bugfixes in stateless block execution * code cleanup - Reduce PR footprint by reverting NewEVM to its original signature - Move the access witness to the block context - prepare for a change in AW semantics Need to store the initial values. - Use the touch helper function, DRY * revert the signature of MustCommit to its original form (#21) fix leaf proofs in stateless execution (#22) * Fixes in witness pre-state * Add the recipient's nonce to the witness * reduce PR footprint and investigate issue in root state calculation * quick build fix cleanup: Remove extra parameter in ToBlock revert ToBlock to its older signature fix import cycle in vm tests fix linter issue fix appveyor build fix nil pointers in tests Add indices, yis and Cis to the block's Verkle proof upgrade geth dependency to drop geth's common dep fix cmd/devp2p tests fix rebase issues quell an appveyor warning fix address touching in SLOAD and SSTORE fix access witness for code size touch target account data before calling make sure the proper locations get touched in (ext)codecopy touch all code pages in execution add pushdata to witness remove useless code in genesis snapshot generation testnet: fix some of the rebase/drift issues Fix verkle proof generation in block fix an issue occuring when chunking past the code size fix: ensure the code copy doesn't extend past the code size
1002 lines
32 KiB
Go
1002 lines
32 KiB
Go
// Copyright 2015 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package vm
|
|
|
|
import (
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
|
|
"github.com/holiman/uint256"
|
|
"golang.org/x/crypto/sha3"
|
|
)
|
|
|
|
func opAdd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
y.Add(&x, y)
|
|
return nil, nil
|
|
}
|
|
|
|
func opSub(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
y.Sub(&x, y)
|
|
return nil, nil
|
|
}
|
|
|
|
func opMul(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
y.Mul(&x, y)
|
|
return nil, nil
|
|
}
|
|
|
|
func opDiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
y.Div(&x, y)
|
|
return nil, nil
|
|
}
|
|
|
|
func opSdiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
y.SDiv(&x, y)
|
|
return nil, nil
|
|
}
|
|
|
|
func opMod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
y.Mod(&x, y)
|
|
return nil, nil
|
|
}
|
|
|
|
func opSmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
y.SMod(&x, y)
|
|
return nil, nil
|
|
}
|
|
|
|
func opExp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
base, exponent := scope.Stack.pop(), scope.Stack.peek()
|
|
exponent.Exp(&base, exponent)
|
|
return nil, nil
|
|
}
|
|
|
|
func opSignExtend(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
back, num := scope.Stack.pop(), scope.Stack.peek()
|
|
num.ExtendSign(num, &back)
|
|
return nil, nil
|
|
}
|
|
|
|
func opNot(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x := scope.Stack.peek()
|
|
x.Not(x)
|
|
return nil, nil
|
|
}
|
|
|
|
func opLt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
if x.Lt(y) {
|
|
y.SetOne()
|
|
} else {
|
|
y.Clear()
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opGt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
if x.Gt(y) {
|
|
y.SetOne()
|
|
} else {
|
|
y.Clear()
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opSlt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
if x.Slt(y) {
|
|
y.SetOne()
|
|
} else {
|
|
y.Clear()
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opSgt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
if x.Sgt(y) {
|
|
y.SetOne()
|
|
} else {
|
|
y.Clear()
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opEq(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
if x.Eq(y) {
|
|
y.SetOne()
|
|
} else {
|
|
y.Clear()
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opIszero(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x := scope.Stack.peek()
|
|
if x.IsZero() {
|
|
x.SetOne()
|
|
} else {
|
|
x.Clear()
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opAnd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
y.And(&x, y)
|
|
return nil, nil
|
|
}
|
|
|
|
func opOr(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
y.Or(&x, y)
|
|
return nil, nil
|
|
}
|
|
|
|
func opXor(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y := scope.Stack.pop(), scope.Stack.peek()
|
|
y.Xor(&x, y)
|
|
return nil, nil
|
|
}
|
|
|
|
func opByte(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
th, val := scope.Stack.pop(), scope.Stack.peek()
|
|
val.Byte(&th)
|
|
return nil, nil
|
|
}
|
|
|
|
func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek()
|
|
if z.IsZero() {
|
|
z.Clear()
|
|
} else {
|
|
z.AddMod(&x, &y, z)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opMulmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek()
|
|
z.MulMod(&x, &y, z)
|
|
return nil, nil
|
|
}
|
|
|
|
// opSHL implements Shift Left
|
|
// The SHL instruction (shift left) pops 2 values from the stack, first arg1 and then arg2,
|
|
// and pushes on the stack arg2 shifted to the left by arg1 number of bits.
|
|
func opSHL(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
// Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
|
|
shift, value := scope.Stack.pop(), scope.Stack.peek()
|
|
if shift.LtUint64(256) {
|
|
value.Lsh(value, uint(shift.Uint64()))
|
|
} else {
|
|
value.Clear()
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// opSHR implements Logical Shift Right
|
|
// The SHR instruction (logical shift right) pops 2 values from the stack, first arg1 and then arg2,
|
|
// and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill.
|
|
func opSHR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
// Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
|
|
shift, value := scope.Stack.pop(), scope.Stack.peek()
|
|
if shift.LtUint64(256) {
|
|
value.Rsh(value, uint(shift.Uint64()))
|
|
} else {
|
|
value.Clear()
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// opSAR implements Arithmetic Shift Right
|
|
// The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2,
|
|
// and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension.
|
|
func opSAR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
shift, value := scope.Stack.pop(), scope.Stack.peek()
|
|
if shift.GtUint64(256) {
|
|
if value.Sign() >= 0 {
|
|
value.Clear()
|
|
} else {
|
|
// Max negative shift: all bits set
|
|
value.SetAllOne()
|
|
}
|
|
return nil, nil
|
|
}
|
|
n := uint(shift.Uint64())
|
|
value.SRsh(value, n)
|
|
return nil, nil
|
|
}
|
|
|
|
func opSha3(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
offset, size := scope.Stack.pop(), scope.Stack.peek()
|
|
data := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
|
|
|
|
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.Config.EnablePreimageRecording {
|
|
evm.StateDB.AddPreimage(interpreter.hasherBuf, data)
|
|
}
|
|
|
|
size.SetBytes(interpreter.hasherBuf[:])
|
|
return nil, nil
|
|
}
|
|
func opAddress(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Address().Bytes()))
|
|
return nil, nil
|
|
}
|
|
|
|
func opBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
slot := scope.Stack.peek()
|
|
address := common.Address(slot.Bytes20())
|
|
slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address))
|
|
return nil, nil
|
|
}
|
|
|
|
func opOrigin(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.push(new(uint256.Int).SetBytes(interpreter.evm.Origin.Bytes()))
|
|
return nil, nil
|
|
}
|
|
func opCaller(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Caller().Bytes()))
|
|
return nil, nil
|
|
}
|
|
|
|
func opCallValue(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
v, _ := uint256.FromBig(scope.Contract.value)
|
|
scope.Stack.push(v)
|
|
return nil, nil
|
|
}
|
|
|
|
func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
x := scope.Stack.peek()
|
|
if offset, overflow := x.Uint64WithOverflow(); !overflow {
|
|
data := getData(scope.Contract.Input, offset, 32)
|
|
x.SetBytes(data)
|
|
} else {
|
|
x.Clear()
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Input))))
|
|
return nil, nil
|
|
}
|
|
|
|
func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
var (
|
|
memOffset = scope.Stack.pop()
|
|
dataOffset = scope.Stack.pop()
|
|
length = scope.Stack.pop()
|
|
)
|
|
dataOffset64, overflow := dataOffset.Uint64WithOverflow()
|
|
if overflow {
|
|
dataOffset64 = 0xffffffffffffffff
|
|
}
|
|
// These values are checked for overflow during gas cost calculation
|
|
memOffset64 := memOffset.Uint64()
|
|
length64 := length.Uint64()
|
|
scope.Memory.Set(memOffset64, length64, getData(scope.Contract.Input, dataOffset64, length64))
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(interpreter.returnData))))
|
|
return nil, nil
|
|
}
|
|
|
|
func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
var (
|
|
memOffset = scope.Stack.pop()
|
|
dataOffset = scope.Stack.pop()
|
|
length = scope.Stack.pop()
|
|
)
|
|
|
|
offset64, overflow := dataOffset.Uint64WithOverflow()
|
|
if overflow {
|
|
return nil, ErrReturnDataOutOfBounds
|
|
}
|
|
// we can reuse dataOffset now (aliasing it for clarity)
|
|
var end = dataOffset
|
|
end.Add(&dataOffset, &length)
|
|
end64, overflow := end.Uint64WithOverflow()
|
|
if overflow || uint64(len(interpreter.returnData)) < end64 {
|
|
return nil, ErrReturnDataOutOfBounds
|
|
}
|
|
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64])
|
|
return nil, nil
|
|
}
|
|
|
|
func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
slot := scope.Stack.peek()
|
|
cs := uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))
|
|
if interpreter.evm.accesses != nil {
|
|
index := trieUtils.GetTreeKeyCodeSize(slot.Bytes())
|
|
interpreter.evm.TxContext.Accesses.TouchAddress(index, uint256.NewInt(cs).Bytes())
|
|
}
|
|
slot.SetUint64(cs)
|
|
return nil, nil
|
|
}
|
|
|
|
func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
l := new(uint256.Int)
|
|
l.SetUint64(uint64(len(scope.Contract.Code)))
|
|
scope.Stack.push(l)
|
|
return nil, nil
|
|
}
|
|
|
|
func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
var (
|
|
memOffset = scope.Stack.pop()
|
|
codeOffset = scope.Stack.pop()
|
|
length = scope.Stack.pop()
|
|
)
|
|
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
|
if overflow {
|
|
uint64CodeOffset = 0xffffffffffffffff
|
|
}
|
|
uint64CodeEnd, overflow := new(uint256.Int).Add(&codeOffset, &length).Uint64WithOverflow()
|
|
if overflow {
|
|
uint64CodeEnd = 0xffffffffffffffff
|
|
}
|
|
if interpreter.evm.accesses != nil {
|
|
copyCodeFromAccesses(scope.Contract.Address(), uint64CodeOffset, uint64CodeEnd, memOffset.Uint64(), interpreter, scope)
|
|
} else {
|
|
codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
|
|
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
|
|
|
touchEachChunks(uint64CodeOffset, uint64CodeEnd, codeCopy, scope.Contract, interpreter.evm)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// Helper function to touch every chunk in a code range
|
|
func touchEachChunks(start, end uint64, code []byte, contract *Contract, evm *EVM) {
|
|
for chunk := start / 31; chunk <= end/31 && chunk <= uint64(len(code))/31; chunk++ {
|
|
index := trieUtils.GetTreeKeyCodeChunk(contract.Address().Bytes(), uint256.NewInt(chunk))
|
|
count := uint64(0)
|
|
// Look for the first code byte (i.e. no pushdata)
|
|
for ; count < 31 && !contract.IsCode(chunk*31+count); count++ {
|
|
}
|
|
var value [32]byte
|
|
value[0] = byte(count)
|
|
end := (chunk + 1) * 31
|
|
if end > uint64(len(code)) {
|
|
end = uint64(len(code))
|
|
}
|
|
copy(value[1:], code[chunk*31:end])
|
|
evm.Accesses.TouchAddress(index, value[:])
|
|
}
|
|
}
|
|
|
|
// copyCodeFromAccesses perform codecopy from the witness, not from the db.
|
|
func copyCodeFromAccesses(addr common.Address, codeOffset, codeEnd, memOffset uint64, in *EVMInterpreter, scope *ScopeContext) {
|
|
chunk := codeOffset / 31
|
|
endChunk := codeEnd / 31
|
|
start := codeOffset % 31 // start inside the first code chunk
|
|
offset := uint64(0) // memory offset to write to
|
|
// XXX uint64 overflow in condition check
|
|
for end := uint64(31); chunk < endChunk; chunk, start = chunk+1, 0 {
|
|
// case of the last chunk: figure out how many bytes need to
|
|
// be extracted from the last chunk.
|
|
if chunk+1 == endChunk {
|
|
end = codeEnd % 31
|
|
}
|
|
|
|
// TODO make a version of GetTreeKeyCodeChunk without the bigint
|
|
index := common.BytesToHash(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk)))
|
|
h := in.evm.accesses[index]
|
|
//in.evm.Accesses.TouchAddress(index.Bytes(), h[1+start:1+end])
|
|
scope.Memory.Set(memOffset+offset, end-start, h[1+start:end])
|
|
offset += 31 - start
|
|
}
|
|
}
|
|
|
|
func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
var (
|
|
stack = scope.Stack
|
|
a = stack.pop()
|
|
memOffset = stack.pop()
|
|
codeOffset = stack.pop()
|
|
length = stack.pop()
|
|
)
|
|
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
|
if overflow {
|
|
uint64CodeOffset = 0xffffffffffffffff
|
|
}
|
|
uint64CodeEnd, overflow := new(uint256.Int).Add(&codeOffset, &length).Uint64WithOverflow()
|
|
if overflow {
|
|
uint64CodeEnd = 0xffffffffffffffff
|
|
}
|
|
addr := common.Address(a.Bytes20())
|
|
if interpreter.evm.accesses != nil {
|
|
copyCodeFromAccesses(addr, uint64CodeOffset, uint64CodeEnd, memOffset.Uint64(), interpreter, scope)
|
|
} else {
|
|
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
|
|
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
|
|
|
touchEachChunks(uint64CodeOffset, uint64CodeEnd, codeCopy, scope.Contract, interpreter.evm)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// opExtCodeHash returns the code hash of a specified account.
|
|
// There are several cases when the function is called, while we can relay everything
|
|
// to `state.GetCodeHash` function to ensure the correctness.
|
|
// (1) Caller tries to get the code hash of a normal contract account, state
|
|
// should return the relative code hash and set it as the result.
|
|
//
|
|
// (2) Caller tries to get the code hash of a non-existent account, state should
|
|
// return common.Hash{} and zero will be set as the result.
|
|
//
|
|
// (3) Caller tries to get the code hash for an account without contract code,
|
|
// state should return emptyCodeHash(0xc5d246...) as the result.
|
|
//
|
|
// (4) Caller tries to get the code hash of a precompiled account, the result
|
|
// should be zero or emptyCodeHash.
|
|
//
|
|
// It is worth noting that in order to avoid unnecessary create and clean,
|
|
// all precompile accounts on mainnet have been transferred 1 wei, so the return
|
|
// here should be emptyCodeHash.
|
|
// If the precompile account is not transferred any amount on a private or
|
|
// customized chain, the return value will be zero.
|
|
//
|
|
// (5) Caller tries to get the code hash for an account which is marked as suicided
|
|
// in the current transaction, the code hash of this account should be returned.
|
|
//
|
|
// (6) Caller tries to get the code hash for an account which is marked as deleted,
|
|
// this account should be regarded as a non-existent account and zero should be returned.
|
|
func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
slot := scope.Stack.peek()
|
|
address := common.Address(slot.Bytes20())
|
|
if interpreter.evm.StateDB.Empty(address) {
|
|
slot.Clear()
|
|
} else {
|
|
slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
v, _ := uint256.FromBig(interpreter.evm.GasPrice)
|
|
scope.Stack.push(v)
|
|
return nil, nil
|
|
}
|
|
|
|
func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
num := scope.Stack.peek()
|
|
num64, overflow := num.Uint64WithOverflow()
|
|
if overflow {
|
|
num.Clear()
|
|
return nil, nil
|
|
}
|
|
var upper, lower uint64
|
|
upper = interpreter.evm.Context.BlockNumber.Uint64()
|
|
if upper < 257 {
|
|
lower = 0
|
|
} else {
|
|
lower = upper - 256
|
|
}
|
|
if num64 >= lower && num64 < upper {
|
|
num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes())
|
|
} else {
|
|
num.Clear()
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opCoinbase(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.push(new(uint256.Int).SetBytes(interpreter.evm.Context.Coinbase.Bytes()))
|
|
return nil, nil
|
|
}
|
|
|
|
func opTimestamp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
v, _ := uint256.FromBig(interpreter.evm.Context.Time)
|
|
scope.Stack.push(v)
|
|
return nil, nil
|
|
}
|
|
|
|
func opNumber(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
v, _ := uint256.FromBig(interpreter.evm.Context.BlockNumber)
|
|
scope.Stack.push(v)
|
|
return nil, nil
|
|
}
|
|
|
|
func opDifficulty(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
v, _ := uint256.FromBig(interpreter.evm.Context.Difficulty)
|
|
scope.Stack.push(v)
|
|
return nil, nil
|
|
}
|
|
|
|
func opGasLimit(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit))
|
|
return nil, nil
|
|
}
|
|
|
|
func opPop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.pop()
|
|
return nil, nil
|
|
}
|
|
|
|
func opMload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
v := scope.Stack.peek()
|
|
offset := int64(v.Uint64())
|
|
v.SetBytes(scope.Memory.GetPtr(offset, 32))
|
|
return nil, nil
|
|
}
|
|
|
|
func opMstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
// pop value of the stack
|
|
mStart, val := scope.Stack.pop(), scope.Stack.pop()
|
|
scope.Memory.Set32(mStart.Uint64(), &val)
|
|
return nil, nil
|
|
}
|
|
|
|
func opMstore8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
off, val := scope.Stack.pop(), scope.Stack.pop()
|
|
scope.Memory.store[off.Uint64()] = byte(val.Uint64())
|
|
return nil, nil
|
|
}
|
|
|
|
func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
loc := scope.Stack.peek()
|
|
hash := common.Hash(loc.Bytes32())
|
|
val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash)
|
|
loc.SetBytes(val.Bytes())
|
|
// Get the initial value as it might not be present
|
|
|
|
index := trieUtils.GetTreeKeyStorageSlot(scope.Contract.Address().Bytes(), loc)
|
|
interpreter.evm.TxContext.Accesses.TouchAddress(index, val.Bytes())
|
|
return nil, nil
|
|
}
|
|
|
|
func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
loc := scope.Stack.pop()
|
|
val := scope.Stack.pop()
|
|
interpreter.evm.StateDB.SetState(scope.Contract.Address(),
|
|
loc.Bytes32(), val.Bytes32())
|
|
return nil, nil
|
|
}
|
|
|
|
func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
pos := scope.Stack.pop()
|
|
if !scope.Contract.validJumpdest(&pos) {
|
|
return nil, ErrInvalidJump
|
|
}
|
|
*pc = pos.Uint64()
|
|
return nil, nil
|
|
}
|
|
|
|
func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
pos, cond := scope.Stack.pop(), scope.Stack.pop()
|
|
if !cond.IsZero() {
|
|
if !scope.Contract.validJumpdest(&pos) {
|
|
return nil, ErrInvalidJump
|
|
}
|
|
*pc = pos.Uint64()
|
|
} else {
|
|
*pc++
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opJumpdest(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func opPc(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.push(new(uint256.Int).SetUint64(*pc))
|
|
return nil, nil
|
|
}
|
|
|
|
func opMsize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.push(new(uint256.Int).SetUint64(uint64(scope.Memory.Len())))
|
|
return nil, nil
|
|
}
|
|
|
|
func opGas(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.push(new(uint256.Int).SetUint64(scope.Contract.Gas))
|
|
return nil, nil
|
|
}
|
|
|
|
func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
var (
|
|
value = scope.Stack.pop()
|
|
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
|
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
|
gas = scope.Contract.Gas
|
|
)
|
|
if interpreter.evm.chainRules.IsEIP150 {
|
|
gas -= gas / 64
|
|
}
|
|
// reuse size int for stackvalue
|
|
stackvalue := size
|
|
|
|
scope.Contract.UseGas(gas)
|
|
//TODO: use uint256.Int instead of converting with toBig()
|
|
var bigVal = big0
|
|
if !value.IsZero() {
|
|
bigVal = value.ToBig()
|
|
}
|
|
|
|
res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, bigVal)
|
|
// Push item on the stack based on the returned error. If the ruleset is
|
|
// homestead we must check for CodeStoreOutOfGasError (homestead only
|
|
// rule) and treat as an error, if the ruleset is frontier we must
|
|
// ignore this error and pretend the operation was successful.
|
|
if interpreter.evm.chainRules.IsHomestead && suberr == ErrCodeStoreOutOfGas {
|
|
stackvalue.Clear()
|
|
} else if suberr != nil && suberr != ErrCodeStoreOutOfGas {
|
|
stackvalue.Clear()
|
|
} else {
|
|
stackvalue.SetBytes(addr.Bytes())
|
|
}
|
|
scope.Stack.push(&stackvalue)
|
|
scope.Contract.Gas += returnGas
|
|
|
|
if suberr == ErrExecutionReverted {
|
|
return res, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
var (
|
|
endowment = scope.Stack.pop()
|
|
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
|
salt = scope.Stack.pop()
|
|
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
|
gas = scope.Contract.Gas
|
|
)
|
|
|
|
// Apply EIP150
|
|
gas -= gas / 64
|
|
scope.Contract.UseGas(gas)
|
|
// reuse size int for stackvalue
|
|
stackvalue := size
|
|
//TODO: use uint256.Int instead of converting with toBig()
|
|
bigEndowment := big0
|
|
if !endowment.IsZero() {
|
|
bigEndowment = endowment.ToBig()
|
|
}
|
|
res, addr, returnGas, suberr := interpreter.evm.Create2(scope.Contract, input, gas,
|
|
bigEndowment, &salt)
|
|
// Push item on the stack based on the returned error.
|
|
if suberr != nil {
|
|
stackvalue.Clear()
|
|
} else {
|
|
stackvalue.SetBytes(addr.Bytes())
|
|
}
|
|
scope.Stack.push(&stackvalue)
|
|
scope.Contract.Gas += returnGas
|
|
|
|
if suberr == ErrExecutionReverted {
|
|
return res, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
stack := scope.Stack
|
|
// Pop gas. The actual gas in interpreter.evm.callGasTemp.
|
|
// We can use this as a temporary value
|
|
temp := stack.pop()
|
|
gas := interpreter.evm.callGasTemp
|
|
// Pop other call parameters.
|
|
addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
|
|
toAddr := common.Address(addr.Bytes20())
|
|
// Get the arguments from the memory.
|
|
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
|
|
|
|
var bigVal = big0
|
|
//TODO: use uint256.Int instead of converting with toBig()
|
|
// By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls),
|
|
// but it would make more sense to extend the usage of uint256.Int
|
|
if !value.IsZero() {
|
|
gas += params.CallStipend
|
|
bigVal = value.ToBig()
|
|
}
|
|
|
|
ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal)
|
|
|
|
if err != nil {
|
|
temp.Clear()
|
|
} else {
|
|
temp.SetOne()
|
|
}
|
|
stack.push(&temp)
|
|
if err == nil || err == ErrExecutionReverted {
|
|
ret = common.CopyBytes(ret)
|
|
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
|
}
|
|
scope.Contract.Gas += returnGas
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
// Pop gas. The actual gas is in interpreter.evm.callGasTemp.
|
|
stack := scope.Stack
|
|
// We use it as a temporary value
|
|
temp := stack.pop()
|
|
gas := interpreter.evm.callGasTemp
|
|
// Pop other call parameters.
|
|
addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
|
|
toAddr := common.Address(addr.Bytes20())
|
|
// Get arguments from the memory.
|
|
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
|
|
|
|
//TODO: use uint256.Int instead of converting with toBig()
|
|
var bigVal = big0
|
|
if !value.IsZero() {
|
|
gas += params.CallStipend
|
|
bigVal = value.ToBig()
|
|
}
|
|
|
|
ret, returnGas, err := interpreter.evm.CallCode(scope.Contract, toAddr, args, gas, bigVal)
|
|
if err != nil {
|
|
temp.Clear()
|
|
} else {
|
|
temp.SetOne()
|
|
}
|
|
stack.push(&temp)
|
|
if err == nil || err == ErrExecutionReverted {
|
|
ret = common.CopyBytes(ret)
|
|
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
|
}
|
|
scope.Contract.Gas += returnGas
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
stack := scope.Stack
|
|
// Pop gas. The actual gas is in interpreter.evm.callGasTemp.
|
|
// We use it as a temporary value
|
|
temp := stack.pop()
|
|
gas := interpreter.evm.callGasTemp
|
|
// Pop other call parameters.
|
|
addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
|
|
toAddr := common.Address(addr.Bytes20())
|
|
// Get arguments from the memory.
|
|
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
|
|
|
|
ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas)
|
|
if err != nil {
|
|
temp.Clear()
|
|
} else {
|
|
temp.SetOne()
|
|
}
|
|
stack.push(&temp)
|
|
if err == nil || err == ErrExecutionReverted {
|
|
ret = common.CopyBytes(ret)
|
|
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
|
}
|
|
scope.Contract.Gas += returnGas
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
// Pop gas. The actual gas is in interpreter.evm.callGasTemp.
|
|
stack := scope.Stack
|
|
// We use it as a temporary value
|
|
temp := stack.pop()
|
|
gas := interpreter.evm.callGasTemp
|
|
// Pop other call parameters.
|
|
addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
|
|
toAddr := common.Address(addr.Bytes20())
|
|
// Get arguments from the memory.
|
|
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
|
|
|
|
ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas)
|
|
if err != nil {
|
|
temp.Clear()
|
|
} else {
|
|
temp.SetOne()
|
|
}
|
|
stack.push(&temp)
|
|
if err == nil || err == ErrExecutionReverted {
|
|
ret = common.CopyBytes(ret)
|
|
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
|
}
|
|
scope.Contract.Gas += returnGas
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
offset, size := scope.Stack.pop(), scope.Stack.pop()
|
|
ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
offset, size := scope.Stack.pop(), scope.Stack.pop()
|
|
ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
beneficiary := scope.Stack.pop()
|
|
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
|
|
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
|
|
interpreter.evm.StateDB.Suicide(scope.Contract.Address())
|
|
if interpreter.cfg.Debug {
|
|
interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
|
|
interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// following functions are used by the instruction jump table
|
|
|
|
// make log instruction function
|
|
func makeLog(size int) executionFunc {
|
|
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
topics := make([]common.Hash, size)
|
|
stack := scope.Stack
|
|
mStart, mSize := stack.pop(), stack.pop()
|
|
for i := 0; i < size; i++ {
|
|
addr := stack.pop()
|
|
topics[i] = addr.Bytes32()
|
|
}
|
|
|
|
d := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64()))
|
|
interpreter.evm.StateDB.AddLog(&types.Log{
|
|
Address: scope.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.Context.BlockNumber.Uint64(),
|
|
})
|
|
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
// opPush1 is a specialized version of pushN
|
|
func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
var (
|
|
codeLen = uint64(len(scope.Contract.Code))
|
|
integer = new(uint256.Int)
|
|
)
|
|
*pc += 1
|
|
if *pc < codeLen {
|
|
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
|
|
// touch next chunk if PUSH1 is at the boundary. if so, *pc has
|
|
// advanced past this boundary.
|
|
if *pc%31 == 0 {
|
|
// touch push data by adding the last byte of the pushdata
|
|
var value [32]byte
|
|
chunk := *pc / 31
|
|
count := uint64(0)
|
|
// Look for the first code byte (i.e. no pushdata)
|
|
for ; count < 31 && !scope.Contract.IsCode(chunk*31+count); count++ {
|
|
}
|
|
value[0] = byte(count)
|
|
endMin := (chunk + 1) * 31
|
|
if endMin > uint64(len(scope.Contract.Code)) {
|
|
endMin = uint64(len(scope.Contract.Code))
|
|
}
|
|
copy(value[1:], scope.Contract.Code[chunk*31:endMin])
|
|
index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk))
|
|
interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
|
|
}
|
|
} else {
|
|
scope.Stack.push(integer.Clear())
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// make push instruction function
|
|
func makePush(size uint64, pushByteSize int) executionFunc {
|
|
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
codeLen := len(scope.Contract.Code)
|
|
|
|
startMin := codeLen
|
|
if int(*pc+1) < startMin {
|
|
startMin = int(*pc + 1)
|
|
}
|
|
|
|
endMin := codeLen
|
|
if startMin+pushByteSize < endMin {
|
|
endMin = startMin + pushByteSize
|
|
}
|
|
|
|
integer := new(uint256.Int)
|
|
scope.Stack.push(integer.SetBytes(common.RightPadBytes(
|
|
scope.Contract.Code[startMin:endMin], pushByteSize)))
|
|
|
|
// touch push data by adding the last byte of the pushdata
|
|
var value [32]byte
|
|
chunk := uint64(endMin-1) / 31
|
|
count := uint64(0)
|
|
// Look for the first code byte (i.e. no pushdata)
|
|
for ; count < 31 && !scope.Contract.IsCode(chunk*31+count); count++ {
|
|
}
|
|
value[0] = byte(count)
|
|
copy(value[1:], scope.Contract.Code[chunk*31:endMin])
|
|
index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk))
|
|
interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
|
|
|
|
// in the case of PUSH32, the end data might be two chunks away,
|
|
// so also get the middle chunk.
|
|
if pushByteSize == 32 {
|
|
chunk = uint64(endMin-2) / 31
|
|
count = uint64(0)
|
|
// Look for the first code byte (i.e. no pushdata)
|
|
for ; count < 31 && !scope.Contract.IsCode(chunk*31+count); count++ {
|
|
}
|
|
value[0] = byte(count)
|
|
copy(value[1:], scope.Contract.Code[chunk*31:(chunk+1)*31])
|
|
index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk))
|
|
interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
|
|
|
|
}
|
|
|
|
*pc += size
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
// make dup instruction function
|
|
func makeDup(size int64) executionFunc {
|
|
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.dup(int(size))
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
// 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, scope *ScopeContext) ([]byte, error) {
|
|
scope.Stack.swap(int(size))
|
|
return nil, nil
|
|
}
|
|
}
|