go-ethereum/core/vm/instructions_test.go
Martin HS 25bc07749c
core/vm: speed up push and interpreter loop (#30662)
Looking at the cpu profile of a burntpix benchmark, I noticed that a lot
of time was spent in gas-used, in the interpreter loop. It's an actual
call (not inlined), which explicitly wants to be ignored by tracing
("tracing.GasChangeIgnored"), so it can be safely and simply inlined.

The other change is in `pushX`. These also do a call to
`common.RightPadBytes`. I replaced that by a doing a corresponding `Lsh`
on the `u256` if needed. Note: it's needed only to make the stack output
look right, for fuzzers. It technically doesn't matter what we put
there: if code ends on a pushdata immediate, nothing will consume the
stack element. We could just as well just ignore it, if we didn't care
about fuzzers (which I do).

Seems quite a lot faster on burntpix, according to my runs. 

This PR:
```
EVM gas used:    5642735088
execution time:  34.84609475s
allocations:     915683
allocated bytes: 175334088
```
```
EVM gas used:    5642735088
execution time:  36.671958278s
allocations:     915701
allocated bytes: 175340528
```

Master
```
EVM gas used:    5642735088
execution time:  49.349209526s
allocations:     915684
allocated bytes: 175333368
```
```
EVM gas used:    5642735088
execution time:  46.581006598s
allocations:     915681
allocated bytes: 175330728
```

---------

Co-authored-by: Sina M <1591639+s1na@users.noreply.github.com>
Co-authored-by: Felix Lange <fjl@twurst.com>
2024-10-30 18:01:47 +01:00

1002 lines
35 KiB
Go

// Copyright 2017 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 (
"bytes"
"encoding/json"
"fmt"
"math/big"
"os"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
type TwoOperandTestcase struct {
X string
Y string
Expected string
}
type twoOperandParams struct {
x string
y string
}
var alphabetSoup = "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
var commonParams []*twoOperandParams
var twoOpMethods map[string]executionFunc
type contractRef struct {
addr common.Address
}
func (c contractRef) Address() common.Address {
return c.addr
}
func init() {
// Params is a list of common edgecases that should be used for some common tests
params := []string{
"0000000000000000000000000000000000000000000000000000000000000000", // 0
"0000000000000000000000000000000000000000000000000000000000000001", // +1
"0000000000000000000000000000000000000000000000000000000000000005", // +5
"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", // + max -1
"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // + max
"8000000000000000000000000000000000000000000000000000000000000000", // - max
"8000000000000000000000000000000000000000000000000000000000000001", // - max+1
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb", // - 5
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // - 1
}
// Params are combined so each param is used on each 'side'
commonParams = make([]*twoOperandParams, len(params)*len(params))
for i, x := range params {
for j, y := range params {
commonParams[i*len(params)+j] = &twoOperandParams{x, y}
}
}
twoOpMethods = map[string]executionFunc{
"add": opAdd,
"sub": opSub,
"mul": opMul,
"div": opDiv,
"sdiv": opSdiv,
"mod": opMod,
"smod": opSmod,
"exp": opExp,
"signext": opSignExtend,
"lt": opLt,
"gt": opGt,
"slt": opSlt,
"sgt": opSgt,
"eq": opEq,
"and": opAnd,
"or": opOr,
"xor": opXor,
"byte": opByte,
"shl": opSHL,
"shr": opSHR,
"sar": opSAR,
}
}
func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
evmInterpreter = env.interpreter
)
for i, test := range tests {
x := new(uint256.Int).SetBytes(common.Hex2Bytes(test.X))
y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Y))
expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected))
stack.push(x)
stack.push(y)
opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data))
}
actual := stack.pop()
if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %v %d, %v(%x, %x): expected %x, got %x", name, i, name, x, y, expected, actual)
}
}
}
func TestByteOp(t *testing.T) {
tests := []TwoOperandTestcase{
{"ABCDEF0908070605040302010000000000000000000000000000000000000000", "00", "AB"},
{"ABCDEF0908070605040302010000000000000000000000000000000000000000", "01", "CD"},
{"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", "00", "00"},
{"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", "01", "CD"},
{"0000000000000000000000000000000000000000000000000000000000102030", "1F", "30"},
{"0000000000000000000000000000000000000000000000000000000000102030", "1E", "20"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "20", "00"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "FFFFFFFFFFFFFFFF", "00"},
}
testTwoOperandOp(t, tests, opByte, "byte")
}
func TestSHL(t *testing.T) {
// Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shl-shift-left
tests := []TwoOperandTestcase{
{"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000002"},
{"0000000000000000000000000000000000000000000000000000000000000001", "ff", "8000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000001", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000001", "0101", "0000000000000000000000000000000000000000000000000000000000000000"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "8000000000000000000000000000000000000000000000000000000000000000"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},
}
testTwoOperandOp(t, tests, opSHL, "shl")
}
func TestSHR(t *testing.T) {
// Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shr-logical-shift-right
tests := []TwoOperandTestcase{
{"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"},
{"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "01", "4000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "ff", "0000000000000000000000000000000000000000000000000000000000000001"},
{"8000000000000000000000000000000000000000000000000000000000000000", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "0101", "0000000000000000000000000000000000000000000000000000000000000000"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "0000000000000000000000000000000000000000000000000000000000000001"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
}
testTwoOperandOp(t, tests, opSHR, "shr")
}
func TestSAR(t *testing.T) {
// Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#sar-arithmetic-shift-right
tests := []TwoOperandTestcase{
{"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"},
{"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "01", "c000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "ff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"8000000000000000000000000000000000000000000000000000000000000000", "0100", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"8000000000000000000000000000000000000000000000000000000000000000", "0101", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
{"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
{"4000000000000000000000000000000000000000000000000000000000000000", "fe", "0000000000000000000000000000000000000000000000000000000000000001"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "f8", "000000000000000000000000000000000000000000000000000000000000007f"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "fe", "0000000000000000000000000000000000000000000000000000000000000001"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "0000000000000000000000000000000000000000000000000000000000000000"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
}
testTwoOperandOp(t, tests, opSAR, "sar")
}
func TestAddMod(t *testing.T) {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
evmInterpreter = NewEVMInterpreter(env)
pc = uint64(0)
)
tests := []struct {
x string
y string
z string
expected string
}{
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe",
},
}
// x + y = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd
// in 256 bit repr, fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd
for i, test := range tests {
x := new(uint256.Int).SetBytes(common.Hex2Bytes(test.x))
y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.y))
z := new(uint256.Int).SetBytes(common.Hex2Bytes(test.z))
expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.expected))
stack.push(z)
stack.push(y)
stack.push(x)
opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
actual := stack.pop()
if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual)
}
}
}
// utility function to fill the json-file with testcases
// Enable this test to generate the 'testcases_xx.json' files
func TestWriteExpectedValues(t *testing.T) {
t.Skip("Enable this test to create json test cases.")
// getResult is a convenience function to generate the expected values
getResult := func(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
interpreter = env.interpreter
)
result := make([]TwoOperandTestcase, len(args))
for i, param := range args {
x := new(uint256.Int).SetBytes(common.Hex2Bytes(param.x))
y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
stack.push(x)
stack.push(y)
opFn(&pc, interpreter, &ScopeContext{nil, stack, nil})
actual := stack.pop()
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
}
return result
}
for name, method := range twoOpMethods {
data, err := json.Marshal(getResult(commonParams, method))
if err != nil {
t.Fatal(err)
}
_ = os.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0644)
if err != nil {
t.Fatal(err)
}
}
}
// TestJsonTestcases runs through all the testcases defined as json-files
func TestJsonTestcases(t *testing.T) {
for name := range twoOpMethods {
data, err := os.ReadFile(fmt.Sprintf("testdata/testcases_%v.json", name))
if err != nil {
t.Fatal("Failed to read file", err)
}
var testcases []TwoOperandTestcase
json.Unmarshal(data, &testcases)
testTwoOperandOp(t, testcases, twoOpMethods[name], name)
}
}
func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
scope = &ScopeContext{nil, stack, nil}
evmInterpreter = NewEVMInterpreter(env)
)
env.interpreter = evmInterpreter
// convert args
intArgs := make([]*uint256.Int, len(args))
for i, arg := range args {
intArgs[i] = new(uint256.Int).SetBytes(common.Hex2Bytes(arg))
}
pc := uint64(0)
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
for _, arg := range intArgs {
stack.push(arg)
}
op(&pc, evmInterpreter, scope)
stack.pop()
}
bench.StopTimer()
for i, arg := range args {
want := new(uint256.Int).SetBytes(common.Hex2Bytes(arg))
if have := intArgs[i]; !want.Eq(have) {
bench.Fatalf("input #%d mutated, have %x want %x", i, have, want)
}
}
}
func BenchmarkOpAdd64(b *testing.B) {
x := "ffffffff"
y := "fd37f3e2bba2c4f"
opBenchmark(b, opAdd, x, y)
}
func BenchmarkOpAdd128(b *testing.B) {
x := "ffffffffffffffff"
y := "f5470b43c6549b016288e9a65629687"
opBenchmark(b, opAdd, x, y)
}
func BenchmarkOpAdd256(b *testing.B) {
x := "0802431afcbce1fc194c9eaa417b2fb67dc75a95db0bc7ec6b1c8af11df6a1da9"
y := "a1f5aac137876480252e5dcac62c354ec0d42b76b0642b6181ed099849ea1d57"
opBenchmark(b, opAdd, x, y)
}
func BenchmarkOpSub64(b *testing.B) {
x := "51022b6317003a9d"
y := "a20456c62e00753a"
opBenchmark(b, opSub, x, y)
}
func BenchmarkOpSub128(b *testing.B) {
x := "4dde30faaacdc14d00327aac314e915d"
y := "9bbc61f5559b829a0064f558629d22ba"
opBenchmark(b, opSub, x, y)
}
func BenchmarkOpSub256(b *testing.B) {
x := "4bfcd8bb2ac462735b48a17580690283980aa2d679f091c64364594df113ea37"
y := "97f9b1765588c4e6b69142eb00d20507301545acf3e1238c86c8b29be227d46e"
opBenchmark(b, opSub, x, y)
}
func BenchmarkOpMul(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opMul, x, y)
}
func BenchmarkOpDiv256(b *testing.B) {
x := "ff3f9014f20db29ae04af2c2d265de17"
y := "fe7fb0d1f59dfe9492ffbf73683fd1e870eec79504c60144cc7f5fc2bad1e611"
opBenchmark(b, opDiv, x, y)
}
func BenchmarkOpDiv128(b *testing.B) {
x := "fdedc7f10142ff97"
y := "fbdfda0e2ce356173d1993d5f70a2b11"
opBenchmark(b, opDiv, x, y)
}
func BenchmarkOpDiv64(b *testing.B) {
x := "fcb34eb3"
y := "f97180878e839129"
opBenchmark(b, opDiv, x, y)
}
func BenchmarkOpSdiv(b *testing.B) {
x := "ff3f9014f20db29ae04af2c2d265de17"
y := "fe7fb0d1f59dfe9492ffbf73683fd1e870eec79504c60144cc7f5fc2bad1e611"
opBenchmark(b, opSdiv, x, y)
}
func BenchmarkOpMod(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opMod, x, y)
}
func BenchmarkOpSmod(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opSmod, x, y)
}
func BenchmarkOpExp(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opExp, x, y)
}
func BenchmarkOpSignExtend(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opSignExtend, x, y)
}
func BenchmarkOpLt(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opLt, x, y)
}
func BenchmarkOpGt(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opGt, x, y)
}
func BenchmarkOpSlt(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opSlt, x, y)
}
func BenchmarkOpSgt(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opSgt, x, y)
}
func BenchmarkOpEq(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opEq, x, y)
}
func BenchmarkOpEq2(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
y := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201fffffffe"
opBenchmark(b, opEq, x, y)
}
func BenchmarkOpAnd(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opAnd, x, y)
}
func BenchmarkOpOr(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opOr, x, y)
}
func BenchmarkOpXor(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opXor, x, y)
}
func BenchmarkOpByte(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
opBenchmark(b, opByte, x, y)
}
func BenchmarkOpAddmod(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
z := alphabetSoup
opBenchmark(b, opAddmod, x, y, z)
}
func BenchmarkOpMulmod(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
z := alphabetSoup
opBenchmark(b, opMulmod, x, y, z)
}
func BenchmarkOpSHL(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
y := "ff"
opBenchmark(b, opSHL, x, y)
}
func BenchmarkOpSHR(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
y := "ff"
opBenchmark(b, opSHR, x, y)
}
func BenchmarkOpSAR(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
y := "ff"
opBenchmark(b, opSAR, x, y)
}
func BenchmarkOpIsZero(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
opBenchmark(b, opIszero, x)
}
func TestOpMstore(t *testing.T) {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env)
)
env.interpreter = evmInterpreter
mem.Resize(64)
pc := uint64(0)
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v)))
stack.push(new(uint256.Int))
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
}
stack.push(new(uint256.Int).SetUint64(0x1))
stack.push(new(uint256.Int))
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
t.Fatalf("Mstore failed to overwrite previous value")
}
}
func BenchmarkOpMstore(bench *testing.B) {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env)
)
env.interpreter = evmInterpreter
mem.Resize(64)
pc := uint64(0)
memStart := new(uint256.Int)
value := new(uint256.Int).SetUint64(0x1337)
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
stack.push(value)
stack.push(memStart)
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
}
}
func TestOpTstore(t *testing.T) {
var (
statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
env = NewEVM(BlockContext{}, TxContext{}, statedb, params.TestChainConfig, Config{})
stack = newstack()
mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env)
caller = common.Address{}
to = common.Address{1}
contractRef = contractRef{caller}
contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0)
scopeContext = ScopeContext{mem, stack, contract}
value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700")
)
// Add a stateObject for the caller and the contract being called
statedb.CreateAccount(caller)
statedb.CreateAccount(to)
env.interpreter = evmInterpreter
pc := uint64(0)
// push the value to the stack
stack.push(new(uint256.Int).SetBytes(value))
// push the location to the stack
stack.push(new(uint256.Int))
opTstore(&pc, evmInterpreter, &scopeContext)
// there should be no elements on the stack after TSTORE
if stack.len() != 0 {
t.Fatal("stack wrong size")
}
// push the location to the stack
stack.push(new(uint256.Int))
opTload(&pc, evmInterpreter, &scopeContext)
// there should be one element on the stack after TLOAD
if stack.len() != 1 {
t.Fatal("stack wrong size")
}
val := stack.peek()
if !bytes.Equal(val.Bytes(), value) {
t.Fatal("incorrect element read from transient storage")
}
}
func BenchmarkOpKeccak256(bench *testing.B) {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env)
)
env.interpreter = evmInterpreter
mem.Resize(32)
pc := uint64(0)
start := new(uint256.Int)
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
stack.push(uint256.NewInt(32))
stack.push(start)
opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
}
}
func TestCreate2Addresses(t *testing.T) {
type testcase struct {
origin string
salt string
code string
expected string
}
for i, tt := range []testcase{
{
origin: "0x0000000000000000000000000000000000000000",
salt: "0x0000000000000000000000000000000000000000",
code: "0x00",
expected: "0x4d1a2e2bb4f88f0250f26ffff098b0b30b26bf38",
},
{
origin: "0xdeadbeef00000000000000000000000000000000",
salt: "0x0000000000000000000000000000000000000000",
code: "0x00",
expected: "0xB928f69Bb1D91Cd65274e3c79d8986362984fDA3",
},
{
origin: "0xdeadbeef00000000000000000000000000000000",
salt: "0xfeed000000000000000000000000000000000000",
code: "0x00",
expected: "0xD04116cDd17beBE565EB2422F2497E06cC1C9833",
},
{
origin: "0x0000000000000000000000000000000000000000",
salt: "0x0000000000000000000000000000000000000000",
code: "0xdeadbeef",
expected: "0x70f2b2914A2a4b783FaEFb75f459A580616Fcb5e",
},
{
origin: "0x00000000000000000000000000000000deadbeef",
salt: "0xcafebabe",
code: "0xdeadbeef",
expected: "0x60f3f640a8508fC6a86d45DF051962668E1e8AC7",
},
{
origin: "0x00000000000000000000000000000000deadbeef",
salt: "0xcafebabe",
code: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
expected: "0x1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C",
},
{
origin: "0x0000000000000000000000000000000000000000",
salt: "0x0000000000000000000000000000000000000000",
code: "0x",
expected: "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0",
},
} {
origin := common.BytesToAddress(common.FromHex(tt.origin))
salt := common.BytesToHash(common.FromHex(tt.salt))
code := common.FromHex(tt.code)
codeHash := crypto.Keccak256(code)
address := crypto.CreateAddress2(origin, salt, codeHash)
/*
stack := newstack()
// salt, but we don't need that for this test
stack.push(big.NewInt(int64(len(code)))) //size
stack.push(big.NewInt(0)) // memstart
stack.push(big.NewInt(0)) // value
gas, _ := gasCreate2(params.GasTable{}, nil, nil, stack, nil, 0)
fmt.Printf("Example %d\n* address `0x%x`\n* salt `0x%x`\n* init_code `0x%x`\n* gas (assuming no mem expansion): `%v`\n* result: `%s`\n\n", i,origin, salt, code, gas, address.String())
*/
expected := common.BytesToAddress(common.FromHex(tt.expected))
if !bytes.Equal(expected.Bytes(), address.Bytes()) {
t.Errorf("test %d: expected %s, got %s", i, expected.String(), address.String())
}
}
}
func TestRandom(t *testing.T) {
type testcase struct {
name string
random common.Hash
}
for _, tt := range []testcase{
{name: "empty hash", random: common.Hash{}},
{name: "1", random: common.Hash{0}},
{name: "emptyCodeHash", random: types.EmptyCodeHash},
{name: "hash(0x010203)", random: crypto.Keccak256Hash([]byte{0x01, 0x02, 0x03})},
} {
var (
env = NewEVM(BlockContext{Random: &tt.random}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
evmInterpreter = env.interpreter
)
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
}
actual := stack.pop()
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.random.Bytes()))
if overflow {
t.Errorf("Testcase %v: invalid overflow", tt.name)
}
if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual)
}
}
}
func TestBlobHash(t *testing.T) {
type testcase struct {
name string
idx uint64
expect common.Hash
hashes []common.Hash
}
var (
zero = common.Hash{0}
one = common.Hash{1}
two = common.Hash{2}
three = common.Hash{3}
)
for _, tt := range []testcase{
{name: "[{1}]", idx: 0, expect: one, hashes: []common.Hash{one}},
{name: "[1,{2},3]", idx: 2, expect: three, hashes: []common.Hash{one, two, three}},
{name: "out-of-bounds (empty)", idx: 10, expect: zero, hashes: []common.Hash{}},
{name: "out-of-bounds", idx: 25, expect: zero, hashes: []common.Hash{one, two, three}},
{name: "out-of-bounds (nil)", idx: 25, expect: zero, hashes: nil},
} {
var (
env = NewEVM(BlockContext{}, TxContext{BlobHashes: tt.hashes}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
evmInterpreter = env.interpreter
)
stack.push(uint256.NewInt(tt.idx))
opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
}
actual := stack.pop()
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.expect.Bytes()))
if overflow {
t.Errorf("Testcase %v: invalid overflow", tt.name)
}
if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual)
}
}
}
func TestOpMCopy(t *testing.T) {
// Test cases from https://eips.ethereum.org/EIPS/eip-5656#test-cases
for i, tc := range []struct {
dst, src, len string
pre string
want string
wantGas uint64
}{
{ // MCOPY 0 32 32 - copy 32 bytes from offset 32 to offset 0.
dst: "0x0", src: "0x20", len: "0x20",
pre: "0000000000000000000000000000000000000000000000000000000000000000 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
want: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
wantGas: 6,
},
{ // MCOPY 0 0 32 - copy 32 bytes from offset 0 to offset 0.
dst: "0x0", src: "0x0", len: "0x20",
pre: "0101010101010101010101010101010101010101010101010101010101010101",
want: "0101010101010101010101010101010101010101010101010101010101010101",
wantGas: 6,
},
{ // MCOPY 0 1 8 - copy 8 bytes from offset 1 to offset 0 (overlapping).
dst: "0x0", src: "0x1", len: "0x8",
pre: "000102030405060708 000000000000000000000000000000000000000000000000",
want: "010203040506070808 000000000000000000000000000000000000000000000000",
wantGas: 6,
},
{ // MCOPY 1 0 8 - copy 8 bytes from offset 0 to offset 1 (overlapping).
dst: "0x1", src: "0x0", len: "0x8",
pre: "000102030405060708 000000000000000000000000000000000000000000000000",
want: "000001020304050607 000000000000000000000000000000000000000000000000",
wantGas: 6,
},
// Tests below are not in the EIP, but maybe should be added
{ // MCOPY 0xFFFFFFFFFFFF 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds index(overlapping).
dst: "0xFFFFFFFFFFFF", src: "0xFFFFFFFFFFFF", len: "0x0",
pre: "11",
want: "11",
wantGas: 3,
},
{ // MCOPY 0xFFFFFFFFFFFF 0 0 - copy zero bytes from start of mem to out-of-bounds.
dst: "0xFFFFFFFFFFFF", src: "0x0", len: "0x0",
pre: "11",
want: "11",
wantGas: 3,
},
{ // MCOPY 0 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds to start of mem
dst: "0x0", src: "0xFFFFFFFFFFFF", len: "0x0",
pre: "11",
want: "11",
wantGas: 3,
},
{ // MCOPY - copy 1 from space outside of uint64 space
dst: "0x0", src: "0x10000000000000000", len: "0x1",
pre: "0",
},
{ // MCOPY - copy 1 from 0 to space outside of uint64
dst: "0x10000000000000000", src: "0x0", len: "0x1",
pre: "0",
},
{ // MCOPY - copy nothing from 0 to space outside of uint64
dst: "0x10000000000000000", src: "0x0", len: "0x0",
pre: "",
want: "",
wantGas: 3,
},
{ // MCOPY - copy 1 from 0x20 to 0x10, with no prior allocated mem
dst: "0x10", src: "0x20", len: "0x1",
pre: "",
// 64 bytes
want: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
wantGas: 12,
},
{ // MCOPY - copy 1 from 0x19 to 0x10, with no prior allocated mem
dst: "0x10", src: "0x19", len: "0x1",
pre: "",
// 32 bytes
want: "0x0000000000000000000000000000000000000000000000000000000000000000",
wantGas: 9,
},
} {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
evmInterpreter = env.interpreter
)
data := common.FromHex(strings.ReplaceAll(tc.pre, " ", ""))
// Set pre
mem := NewMemory()
mem.Resize(uint64(len(data)))
mem.Set(0, uint64(len(data)), data)
// Push stack args
len, _ := uint256.FromHex(tc.len)
src, _ := uint256.FromHex(tc.src)
dst, _ := uint256.FromHex(tc.dst)
stack.push(len)
stack.push(src)
stack.push(dst)
wantErr := (tc.wantGas == 0)
// Calc mem expansion
var memorySize uint64
if memSize, overflow := memoryMcopy(stack); overflow {
if wantErr {
continue
}
t.Errorf("overflow")
} else {
var overflow bool
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
t.Error(ErrGasUintOverflow)
}
}
// and the dynamic cost
var haveGas uint64
if dynamicCost, err := gasMcopy(env, nil, stack, mem, memorySize); err != nil {
t.Error(err)
} else {
haveGas = GasFastestStep + dynamicCost
}
// Expand mem
if memorySize > 0 {
mem.Resize(memorySize)
}
// Do the copy
opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
want := common.FromHex(strings.ReplaceAll(tc.want, " ", ""))
if have := mem.store; !bytes.Equal(want, have) {
t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have)
}
wantGas := tc.wantGas
if haveGas != wantGas {
t.Errorf("case %d: gas wrong, want %d have %d\n", i, wantGas, haveGas)
}
}
}
// TestPush sanity-checks how code with immediates are handled when the code size is
// smaller than the size of the immediate.
func TestPush(t *testing.T) {
code := common.FromHex("0011223344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121")
push32 := makePush(32, 32)
scope := &ScopeContext{
Memory: nil,
Stack: newstack(),
Contract: &Contract{
Code: code,
},
}
for i, want := range []string{
"0x11223344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1",
"0x223344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1",
"0x3344556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1",
"0x44556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1",
"0x556677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1",
"0x6677889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1",
"0x77889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191",
"0x889900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181",
"0x9900aabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171",
"0xaabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191817161",
"0xaabbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151",
"0xbbccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141",
"0xccddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191817161514131",
"0xddeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121",
"0xeeff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100",
"0xff0102030405060708090a0b0c0d0e0ff1e1d1c1b1a191817161514131210000",
"0x102030405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121000000",
"0x2030405060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100000000",
"0x30405060708090a0b0c0d0e0ff1e1d1c1b1a191817161514131210000000000",
"0x405060708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121000000000000",
"0x5060708090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100000000000000",
"0x60708090a0b0c0d0e0ff1e1d1c1b1a191817161514131210000000000000000",
"0x708090a0b0c0d0e0ff1e1d1c1b1a19181716151413121000000000000000000",
"0x8090a0b0c0d0e0ff1e1d1c1b1a1918171615141312100000000000000000000",
"0x90a0b0c0d0e0ff1e1d1c1b1a191817161514131210000000000000000000000",
"0xa0b0c0d0e0ff1e1d1c1b1a19181716151413121000000000000000000000000",
"0xb0c0d0e0ff1e1d1c1b1a1918171615141312100000000000000000000000000",
"0xc0d0e0ff1e1d1c1b1a191817161514131210000000000000000000000000000",
"0xd0e0ff1e1d1c1b1a19181716151413121000000000000000000000000000000",
"0xe0ff1e1d1c1b1a1918171615141312100000000000000000000000000000000",
"0xff1e1d1c1b1a191817161514131210000000000000000000000000000000000",
"0xf1e1d1c1b1a19181716151413121000000000000000000000000000000000000",
"0xe1d1c1b1a1918171615141312100000000000000000000000000000000000000",
"0xd1c1b1a191817161514131210000000000000000000000000000000000000000",
"0xc1b1a19181716151413121000000000000000000000000000000000000000000",
"0xb1a1918171615141312100000000000000000000000000000000000000000000",
"0xa191817161514131210000000000000000000000000000000000000000000000",
"0x9181716151413121000000000000000000000000000000000000000000000000",
"0x8171615141312100000000000000000000000000000000000000000000000000",
"0x7161514131210000000000000000000000000000000000000000000000000000",
"0x6151413121000000000000000000000000000000000000000000000000000000",
"0x5141312100000000000000000000000000000000000000000000000000000000",
"0x4131210000000000000000000000000000000000000000000000000000000000",
"0x3121000000000000000000000000000000000000000000000000000000000000",
"0x2100000000000000000000000000000000000000000000000000000000000000",
"0x0",
} {
pc := new(uint64)
*pc = uint64(i)
push32(pc, nil, scope)
res := scope.Stack.pop()
if have := res.Hex(); have != want {
t.Fatalf("case %d, have %v want %v", i, have, want)
}
}
}