mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
core/vm: add eof container
This commit is contained in:
parent
183e7b702a
commit
9354ef082b
22 changed files with 1942 additions and 98 deletions
163
cmd/evm/eofparser.go
Normal file
163
cmd/evm/eofparser.go
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
jt = vm.NewShanghaiEOFInstructionSetForTesting()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
jt vm.JumpTable
|
||||||
|
errorMap = map[string]int{
|
||||||
|
io.ErrUnexpectedEOF.Error(): 1,
|
||||||
|
vm.ErrInvalidMagic.Error(): 2,
|
||||||
|
vm.ErrInvalidVersion.Error(): 3,
|
||||||
|
vm.ErrMissingTypeHeader.Error(): 4,
|
||||||
|
vm.ErrInvalidTypeSize.Error(): 5,
|
||||||
|
vm.ErrMissingCodeHeader.Error(): 6,
|
||||||
|
vm.ErrInvalidCodeHeader.Error(): 7,
|
||||||
|
vm.ErrMissingDataHeader.Error(): 8,
|
||||||
|
vm.ErrMissingTerminator.Error(): 9,
|
||||||
|
vm.ErrTooManyInputs.Error(): 10,
|
||||||
|
vm.ErrTooManyOutputs.Error(): 11,
|
||||||
|
vm.ErrTooLargeMaxStackHeight.Error(): 12,
|
||||||
|
vm.ErrInvalidCodeSize.Error(): 13,
|
||||||
|
vm.ErrInvalidContainerSize.Error(): 14,
|
||||||
|
vm.ErrUndefinedInstruction.Error(): 15,
|
||||||
|
vm.ErrTruncatedImmediate.Error(): 16,
|
||||||
|
vm.ErrInvalidSectionArgument.Error(): 17,
|
||||||
|
vm.ErrInvalidJumpDest.Error(): 18,
|
||||||
|
vm.ErrConflictingStack.Error(): 19,
|
||||||
|
vm.ErrInvalidBranchCount.Error(): 20,
|
||||||
|
vm.ErrInvalidOutputs.Error(): 21,
|
||||||
|
vm.ErrInvalidMaxStackHeight.Error(): 22,
|
||||||
|
vm.ErrInvalidCodeTermination.Error(): 23,
|
||||||
|
vm.ErrUnreachableCode.Error(): 24,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type EOFTest struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Results map[string]etResult `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type etResult struct {
|
||||||
|
Result bool `json:"result"`
|
||||||
|
Exception int `json:"exception,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func eofParser(ctx *cli.Context) error {
|
||||||
|
// If `--hex` is set, parse and validate the hex string argument.
|
||||||
|
if ctx.IsSet(HexFlag.Name) {
|
||||||
|
if _, err := parseAndValidate(ctx.String(HexFlag.Name)); err != nil {
|
||||||
|
if err2 := errors.Unwrap(err); err2 != nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
return fmt.Errorf("err(%d): %w", errorMap[err.Error()], err)
|
||||||
|
}
|
||||||
|
fmt.Println("ok.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `--test` is set, parse and validate the reference test at the provided path.
|
||||||
|
if ctx.IsSet(RefTestFlag.Name) {
|
||||||
|
src, err := os.ReadFile(ctx.String(RefTestFlag.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var tests map[string]EOFTest
|
||||||
|
if err = json.Unmarshal(src, &tests); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
passed, total := 0, 0
|
||||||
|
for name, tt := range tests {
|
||||||
|
for fork, r := range tt.Results {
|
||||||
|
total++
|
||||||
|
// TODO(matt): all tests currently run against
|
||||||
|
// shanghai EOF, add support for custom forks.
|
||||||
|
_, err := parseAndValidate(tt.Code)
|
||||||
|
if err2 := errors.Unwrap(err); err2 != nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
if r.Result && err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s, %s: expected success, got %v\n", name, fork, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !r.Result && err == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s, %s: expected error %d, got %v\n", name, fork, r.Exception, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !r.Result && err != nil && r.Exception != errorMap[err.Error()] {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s, %s: expected error %d, got: err(%d): %v\n", name, fork, r.Exception, errorMap[err.Error()], err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
passed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("%d/%d tests passed.\n", passed, total)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If neither are passed in, read input from stdin.
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
for scanner.Scan() {
|
||||||
|
t := strings.TrimSpace(scanner.Text())
|
||||||
|
if len(t) == 0 || t[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := parseAndValidate(t); err != nil {
|
||||||
|
if err2 := errors.Unwrap(err); err2 != nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "err(%d): %v\n", errorMap[err.Error()], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAndValidate(s string) (*vm.Container, error) {
|
||||||
|
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
|
||||||
|
s = s[2:]
|
||||||
|
}
|
||||||
|
b, err := hex.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to decode data: %w", err)
|
||||||
|
}
|
||||||
|
var c vm.Container
|
||||||
|
if err := c.UnmarshalBinary(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.ValidateCode(&jt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
@ -196,6 +196,9 @@ func Transition(ctx *cli.Context) error {
|
||||||
if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil {
|
if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := applyEOFChecks(&prestate, chainConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Run the test and aggregate the result
|
// Run the test and aggregate the result
|
||||||
s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name), getTracer)
|
s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name), getTracer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -285,6 +288,29 @@ func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyEOFChecks(prestate *Prestate, chainConfig *params.ChainConfig) error {
|
||||||
|
// Sanity check pre-allocated EOF code to not panic in state transition.
|
||||||
|
if chainConfig.IsShanghai(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) {
|
||||||
|
for addr, acc := range prestate.Pre {
|
||||||
|
if vm.HasEOFByte(acc.Code) {
|
||||||
|
var (
|
||||||
|
c vm.Container
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
err = c.UnmarshalBinary(acc.Code)
|
||||||
|
if err == nil {
|
||||||
|
jt := vm.NewShanghaiEOFInstructionSetForTesting()
|
||||||
|
err = c.ValidateCode(&jt)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return NewError(ErrorConfig, fmt.Errorf("code at %s considered invalid: %v", addr, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Alloc map[common.Address]types.Account
|
type Alloc map[common.Address]types.Account
|
||||||
|
|
||||||
func (g Alloc) OnRoot(common.Hash) {}
|
func (g Alloc) OnRoot(common.Hash) {}
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool"
|
"github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/internal/flags"
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
|
"github.com/ethereum/go-ethereum/tests"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
// Force-load the tracer engines to trigger registration
|
// Force-load the tracer engines to trigger registration
|
||||||
|
|
@ -138,6 +141,26 @@ var (
|
||||||
Usage: "enable return data output",
|
Usage: "enable return data output",
|
||||||
Category: flags.VMCategory,
|
Category: flags.VMCategory,
|
||||||
}
|
}
|
||||||
|
HexFlag = &cli.StringFlag{
|
||||||
|
Name: "hex",
|
||||||
|
Usage: "single container data parse and validation",
|
||||||
|
}
|
||||||
|
ForknameFlag = &cli.StringFlag{
|
||||||
|
Name: "state.fork",
|
||||||
|
Usage: fmt.Sprintf("Name of ruleset to use."+
|
||||||
|
"\n\tAvailable forknames:"+
|
||||||
|
"\n\t %v"+
|
||||||
|
"\n\tAvailable extra eips:"+
|
||||||
|
"\n\t %v"+
|
||||||
|
"\n\tSyntax <forkname>(+ExtraEip)",
|
||||||
|
strings.Join(tests.AvailableForks(), "\n\t "),
|
||||||
|
strings.Join(vm.ActivateableEips(), ", ")),
|
||||||
|
Value: "Shanghai",
|
||||||
|
}
|
||||||
|
RefTestFlag = &cli.StringFlag{
|
||||||
|
Name: "test",
|
||||||
|
Usage: "Path to EOF validation reference test.",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var stateTransitionCommand = &cli.Command{
|
var stateTransitionCommand = &cli.Command{
|
||||||
|
|
@ -222,6 +245,17 @@ var traceFlags = []cli.Flag{
|
||||||
DisableReturnDataFlag,
|
DisableReturnDataFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var eofParserCommand = &cli.Command{
|
||||||
|
Name: "eofparser",
|
||||||
|
Aliases: []string{"eof"},
|
||||||
|
Usage: "parses hex eof container and returns validation errors (if any)",
|
||||||
|
Action: eofParser,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
HexFlag,
|
||||||
|
RefTestFlag,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var app = flags.NewApp("the evm command line interface")
|
var app = flags.NewApp("the evm command line interface")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -235,6 +269,7 @@ func init() {
|
||||||
stateTransitionCommand,
|
stateTransitionCommand,
|
||||||
transactionCommand,
|
transactionCommand,
|
||||||
blockBuilderCommand,
|
blockBuilderCommand,
|
||||||
|
eofParserCommand,
|
||||||
}
|
}
|
||||||
app.Before = func(ctx *cli.Context) error {
|
app.Before = func(ctx *cli.Context) error {
|
||||||
flags.MigrateGlobalFlags(ctx)
|
flags.MigrateGlobalFlags(ctx)
|
||||||
|
|
|
||||||
|
|
@ -284,8 +284,6 @@ func TestT8n(t *testing.T) {
|
||||||
input: t8nInput{
|
input: t8nInput{
|
||||||
"alloc.json", "txs_more.rlp", "env.json", "Cancun", "",
|
"alloc.json", "txs_more.rlp", "env.json", "Cancun", "",
|
||||||
},
|
},
|
||||||
output: t8nOutput{alloc: true, result: true},
|
|
||||||
expOut: "exp.json",
|
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
args := []string{"t8n"}
|
args := []string{"t8n"}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -4422,9 +4423,6 @@ func TestRequests(t *testing.T) {
|
||||||
t.Fatalf("failed to create tester chain: %v", err)
|
t.Fatalf("failed to create tester chain: %v", err)
|
||||||
}
|
}
|
||||||
defer chain.Stop()
|
defer chain.Stop()
|
||||||
if n, err := chain.InsertChain(blocks); err != nil {
|
|
||||||
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the withdrawal requests match.
|
// Verify the withdrawal requests match.
|
||||||
block := chain.GetBlockByNumber(1)
|
block := chain.GetBlockByNumber(1)
|
||||||
|
|
@ -4476,6 +4474,352 @@ func TestRequests(t *testing.T) {
|
||||||
}
|
}
|
||||||
if want.TargetPublicKey != got.TargetPublicKey {
|
if want.TargetPublicKey != got.TargetPublicKey {
|
||||||
t.Fatalf("wrong target public key: want %s, got %s", common.Bytes2Hex(want.TargetPublicKey[:]), common.Bytes2Hex(got.TargetPublicKey[:]))
|
t.Fatalf("wrong target public key: want %s, got %s", common.Bytes2Hex(want.TargetPublicKey[:]), common.Bytes2Hex(got.TargetPublicKey[:]))
|
||||||
|
}// Verify the withdrawal requests match.
|
||||||
|
block := chain.GetBlockByNumber(1)
|
||||||
|
if block == nil {
|
||||||
|
t.Fatalf("failed to retrieve block 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the withdrawal requests match.
|
||||||
|
got := block.Requests()
|
||||||
|
if len(got) != 2 {
|
||||||
|
t.Fatalf("wrong number of withdrawal requests: wanted 2, got %d", len(got))
|
||||||
|
}
|
||||||
|
for i, want := range wxs {
|
||||||
|
got, ok := got[i].Inner().(*types.WithdrawalRequest)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected withdrawal request")
|
||||||
|
}
|
||||||
|
if want.Source != got.Source {
|
||||||
|
t.Fatalf("wrong source address: want %s, got %s", want.Source, got.Source)
|
||||||
|
}
|
||||||
|
if want.PublicKey != got.PublicKey {
|
||||||
|
t.Fatalf("wrong public key: want %s, got %s", common.Bytes2Hex(want.PublicKey[:]), common.Bytes2Hex(got.PublicKey[:]))
|
||||||
|
}
|
||||||
|
if want.Amount != got.Amount {
|
||||||
|
t.Fatalf("wrong amount: want %d, got %d", want.Amount, got.Amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify the consolidation requests match. Even though both requests are sent
|
||||||
|
// in block two, only one is dequeued at a time.
|
||||||
|
for i, want := range cxs {
|
||||||
|
block := chain.GetBlockByNumber(uint64(i + 2))
|
||||||
|
if block == nil {
|
||||||
|
t.Fatalf("failed to retrieve block")
|
||||||
|
}
|
||||||
|
requests := block.Requests()
|
||||||
|
if len(requests) != 1 {
|
||||||
|
t.Fatalf("wrong number of consolidation requests: wanted 1, got %d", len(got))
|
||||||
|
}
|
||||||
|
got, ok := requests[0].Inner().(*types.ConsolidationRequest)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected consolidation request")
|
||||||
|
}
|
||||||
|
if want.Source != got.Source {
|
||||||
|
t.Fatalf("wrong source address: want %s, got %s", want.Source, got.Source)
|
||||||
|
}
|
||||||
|
if want.SourcePublicKey != got.SourcePublicKey {
|
||||||
|
t.Fatalf("wrong source public key: want %s, got %s", common.Bytes2Hex(want.SourcePublicKey[:]), common.Bytes2Hex(got.SourcePublicKey[:]))
|
||||||
|
}
|
||||||
|
if want.TargetPublicKey != got.TargetPublicKey {
|
||||||
|
t.Fatalf("wrong target public key: want %s, got %s", common.Bytes2Hex(want.TargetPublicKey[:]), common.Bytes2Hex(got.TargetPublicKey[:]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func int8ToByte(n int8) uint8 {
|
||||||
|
return uint8(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEOF(t *testing.T) {
|
||||||
|
var (
|
||||||
|
createDeployer = []byte{
|
||||||
|
byte(vm.CALLDATASIZE), // size
|
||||||
|
byte(vm.PUSH1), 0x00, // offset
|
||||||
|
byte(vm.PUSH1), 0x00, // dst
|
||||||
|
byte(vm.CALLDATACOPY),
|
||||||
|
byte(vm.CALLDATASIZE), // len
|
||||||
|
byte(vm.PUSH1), 0x00, // offset
|
||||||
|
byte(vm.PUSH1), 0x00, // value
|
||||||
|
byte(vm.CREATE),
|
||||||
|
}
|
||||||
|
create2Deployer = []byte{
|
||||||
|
byte(vm.CALLDATASIZE), // len
|
||||||
|
byte(vm.PUSH1), 0x00, // offset
|
||||||
|
byte(vm.PUSH1), 0x00, // dst
|
||||||
|
byte(vm.CALLDATACOPY),
|
||||||
|
byte(vm.PUSH1), 0x00, // salt
|
||||||
|
byte(vm.CALLDATASIZE), // len
|
||||||
|
byte(vm.PUSH1), 0x00, // offset
|
||||||
|
byte(vm.PUSH1), 0x00, // value
|
||||||
|
byte(vm.CREATE2),
|
||||||
|
}
|
||||||
|
|
||||||
|
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
|
||||||
|
bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb")
|
||||||
|
cc = common.HexToAddress("0x000000000000000000000000000000000000cccc")
|
||||||
|
engine = beacon.NewFaker()
|
||||||
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||||
|
gspec = &Genesis{
|
||||||
|
Config: params.AllDevChainProtocolChanges,
|
||||||
|
Alloc: GenesisAlloc{
|
||||||
|
addr: {Balance: funds},
|
||||||
|
bb: {Code: createDeployer, Balance: big.NewInt(0)},
|
||||||
|
cc: {Code: create2Deployer, Balance: big.NewInt(0)},
|
||||||
|
aa: {
|
||||||
|
Code: (&vm.Container{
|
||||||
|
Types: []*vm.FunctionMetadata{
|
||||||
|
{Input: 0, Output: 0x80, MaxStackHeight: 0},
|
||||||
|
{Input: 0, Output: 0, MaxStackHeight: 2},
|
||||||
|
{Input: 0, Output: 0, MaxStackHeight: 0},
|
||||||
|
{Input: 0, Output: 0, MaxStackHeight: 2},
|
||||||
|
},
|
||||||
|
Code: [][]byte{
|
||||||
|
{
|
||||||
|
byte(vm.CALLF),
|
||||||
|
byte(0),
|
||||||
|
byte(1),
|
||||||
|
byte(vm.CALLF),
|
||||||
|
byte(0),
|
||||||
|
byte(2),
|
||||||
|
byte(vm.STOP),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
byte(vm.PUSH1),
|
||||||
|
byte(2),
|
||||||
|
byte(vm.RJUMP), // skip first flag
|
||||||
|
byte(0),
|
||||||
|
byte(5),
|
||||||
|
|
||||||
|
byte(vm.PUSH1),
|
||||||
|
byte(1),
|
||||||
|
byte(vm.PUSH1),
|
||||||
|
byte(0),
|
||||||
|
byte(vm.SSTORE), // set first flag
|
||||||
|
|
||||||
|
byte(vm.PUSH1),
|
||||||
|
byte(1),
|
||||||
|
byte(vm.SWAP1),
|
||||||
|
byte(vm.SUB),
|
||||||
|
byte(vm.DUP1),
|
||||||
|
byte(vm.RJUMPI), // jump to first flag, then don't branch
|
||||||
|
byte(0xff),
|
||||||
|
int8ToByte(-13),
|
||||||
|
|
||||||
|
byte(vm.PUSH1),
|
||||||
|
byte(1),
|
||||||
|
byte(vm.PUSH1),
|
||||||
|
byte(1),
|
||||||
|
byte(vm.SSTORE), // set second flag
|
||||||
|
byte(vm.RETF),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
byte(vm.PUSH1),
|
||||||
|
byte(1),
|
||||||
|
byte(vm.PUSH1),
|
||||||
|
byte(2),
|
||||||
|
byte(vm.SSTORE), // set third flag
|
||||||
|
|
||||||
|
byte(vm.CALLF),
|
||||||
|
byte(0),
|
||||||
|
byte(3),
|
||||||
|
byte(vm.RETF),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
byte(vm.PUSH1),
|
||||||
|
byte(0),
|
||||||
|
byte(vm.RJUMPV), // jump over invalid op
|
||||||
|
byte(1),
|
||||||
|
byte(0),
|
||||||
|
byte(1),
|
||||||
|
|
||||||
|
byte(vm.INVALID),
|
||||||
|
|
||||||
|
byte(vm.PUSH1),
|
||||||
|
byte(1),
|
||||||
|
byte(vm.PUSH1),
|
||||||
|
byte(3),
|
||||||
|
byte(vm.SSTORE), // set forth flag
|
||||||
|
byte(vm.RETF),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Data: []byte{},
|
||||||
|
}).MarshalBinary(),
|
||||||
|
Nonce: 0,
|
||||||
|
Balance: big.NewInt(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
gspec.Config.BerlinBlock = common.Big0
|
||||||
|
gspec.Config.LondonBlock = common.Big0
|
||||||
|
gspec.Config.ShanghaiTime = u64(0)
|
||||||
|
signer := types.LatestSigner(gspec.Config)
|
||||||
|
|
||||||
|
container := vm.Container{
|
||||||
|
Types: []*vm.FunctionMetadata{{Input: 0, Output: 0x80, MaxStackHeight: 0}},
|
||||||
|
Code: [][]byte{{byte(vm.STOP)}},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
deployed := container.MarshalBinary()
|
||||||
|
makeDeployCode := func(b []byte) []byte {
|
||||||
|
out := []byte{
|
||||||
|
byte(vm.PUSH1), byte(len(b)), // len
|
||||||
|
byte(vm.PUSH1), 0x0c, // offset
|
||||||
|
byte(vm.PUSH1), 0x00, // dst offset
|
||||||
|
byte(vm.CODECOPY),
|
||||||
|
|
||||||
|
// code in memory
|
||||||
|
byte(vm.PUSH1), byte(len(b)), // size
|
||||||
|
byte(vm.PUSH1), 0x00, // offset
|
||||||
|
byte(vm.RETURN),
|
||||||
|
}
|
||||||
|
return append(out, b...)
|
||||||
|
}
|
||||||
|
initCode := makeDeployCode(deployed)
|
||||||
|
initHash := crypto.Keccak256Hash(initCode)
|
||||||
|
|
||||||
|
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||||
|
b.SetCoinbase(aa)
|
||||||
|
|
||||||
|
// 0: execute flag contract
|
||||||
|
txdata := &types.DynamicFeeTx{
|
||||||
|
ChainID: gspec.Config.ChainID,
|
||||||
|
Nonce: 0,
|
||||||
|
To: &aa,
|
||||||
|
Gas: 500000,
|
||||||
|
GasFeeCap: newGwei(5),
|
||||||
|
GasTipCap: big.NewInt(2),
|
||||||
|
AccessList: nil,
|
||||||
|
Data: []byte{},
|
||||||
|
}
|
||||||
|
tx := types.NewTx(txdata)
|
||||||
|
tx, _ = types.SignTx(tx, signer, key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
|
||||||
|
// 1: deploy eof contract from eoa
|
||||||
|
txdata = &types.DynamicFeeTx{
|
||||||
|
ChainID: gspec.Config.ChainID,
|
||||||
|
Nonce: 1,
|
||||||
|
To: nil,
|
||||||
|
Gas: 500000,
|
||||||
|
GasFeeCap: newGwei(5),
|
||||||
|
GasTipCap: big.NewInt(2),
|
||||||
|
AccessList: nil,
|
||||||
|
Data: initCode,
|
||||||
|
}
|
||||||
|
tx = types.NewTx(txdata)
|
||||||
|
tx, _ = types.SignTx(tx, signer, key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
|
||||||
|
// 2: invalid initcode in create tx, should be valid and use all gas
|
||||||
|
invalid := (&vm.Container{
|
||||||
|
Types: []*vm.FunctionMetadata{{Input: 0, Output: 0x80, MaxStackHeight: 1}},
|
||||||
|
Code: [][]byte{common.Hex2Bytes("604200")},
|
||||||
|
Data: []byte{0x01, 0x02, 0x03},
|
||||||
|
}).MarshalBinary()
|
||||||
|
invalid[2] = 0x02 // make version invalid
|
||||||
|
txdata = &types.DynamicFeeTx{
|
||||||
|
ChainID: gspec.Config.ChainID,
|
||||||
|
Nonce: 2,
|
||||||
|
To: nil,
|
||||||
|
Gas: 500000,
|
||||||
|
GasFeeCap: newGwei(5),
|
||||||
|
GasTipCap: big.NewInt(2),
|
||||||
|
AccessList: nil,
|
||||||
|
Data: invalid,
|
||||||
|
}
|
||||||
|
tx = types.NewTx(txdata)
|
||||||
|
tx, _ = types.SignTx(tx, signer, key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
|
||||||
|
// 3: invalid deployed eof in create tx, should be valid and use all gas
|
||||||
|
inner := (&vm.Container{
|
||||||
|
Types: []*vm.FunctionMetadata{{Input: 0, Output: 0x80, MaxStackHeight: 0}},
|
||||||
|
Code: [][]byte{common.Hex2Bytes("0000")},
|
||||||
|
}).MarshalBinary()
|
||||||
|
invalid = makeDeployCode(inner)
|
||||||
|
txdata = &types.DynamicFeeTx{
|
||||||
|
ChainID: gspec.Config.ChainID,
|
||||||
|
Nonce: 3,
|
||||||
|
To: nil,
|
||||||
|
Gas: 500000,
|
||||||
|
GasFeeCap: newGwei(5),
|
||||||
|
GasTipCap: big.NewInt(2),
|
||||||
|
AccessList: nil,
|
||||||
|
Data: invalid,
|
||||||
|
}
|
||||||
|
tx = types.NewTx(txdata)
|
||||||
|
tx, _ = types.SignTx(tx, signer, key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
|
||||||
|
// 4: deploy eof contract from create contract
|
||||||
|
txdata = &types.DynamicFeeTx{
|
||||||
|
ChainID: gspec.Config.ChainID,
|
||||||
|
Nonce: 4,
|
||||||
|
To: &bb,
|
||||||
|
Gas: 500000,
|
||||||
|
GasFeeCap: newGwei(5),
|
||||||
|
GasTipCap: big.NewInt(2),
|
||||||
|
AccessList: nil,
|
||||||
|
Data: initCode,
|
||||||
|
}
|
||||||
|
tx = types.NewTx(txdata)
|
||||||
|
tx, _ = types.SignTx(tx, signer, key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
|
||||||
|
// 5: deploy eof contract from create2 contract
|
||||||
|
txdata = &types.DynamicFeeTx{
|
||||||
|
ChainID: gspec.Config.ChainID,
|
||||||
|
Nonce: 5,
|
||||||
|
To: &cc,
|
||||||
|
Gas: 500000,
|
||||||
|
GasFeeCap: newGwei(5),
|
||||||
|
GasTipCap: big.NewInt(2),
|
||||||
|
AccessList: nil,
|
||||||
|
Data: initCode,
|
||||||
|
}
|
||||||
|
tx = types.NewTx(txdata)
|
||||||
|
tx, _ = types.SignTx(tx, signer, key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
})
|
||||||
|
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create tester chain: %v", err)
|
||||||
|
}
|
||||||
|
if n, err := chain.InsertChain(blocks); err != nil {
|
||||||
|
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check flags.
|
||||||
|
state, _ := chain.State()
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
if state.GetState(aa, common.BigToHash(big.NewInt(int64(i)))).Big().Uint64() != 1 {
|
||||||
|
t.Fatalf("flag %d not set", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r := chain.GetReceiptsByHash(blocks[0].Hash())[2]
|
||||||
|
if got, want := r.GasUsed, blocks[0].Transactions()[2].Gas(); r.Status == types.ReceiptStatusFailed && got != want {
|
||||||
|
t.Fatalf("gas accounting invalid for create tx with invalid initcode: gasUsed %d, gasLimit %d", got, want)
|
||||||
|
}
|
||||||
|
r = chain.GetReceiptsByHash(blocks[0].Hash())[3]
|
||||||
|
if got, want := r.GasUsed, blocks[0].Transactions()[3].Gas(); r.Status == types.ReceiptStatusFailed && got != want {
|
||||||
|
t.Fatalf("gas accounting invalid for create tx with invalid deployed code: gasUsed %d, gasLimit %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check various deployment mechanisms.
|
||||||
|
if !bytes.Equal(state.GetCode(crypto.CreateAddress(addr, 1)), deployed) {
|
||||||
|
t.Fatalf("failed to deploy EOF with EOA")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(state.GetCode(crypto.CreateAddress(bb, 0)), deployed) {
|
||||||
|
t.Fatalf("failed to deploy EOF with CREATE")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(state.GetCode(crypto.CreateAddress2(cc, [32]byte{}, initHash.Bytes())), deployed) {
|
||||||
|
t.Fatalf("failed to deploy EOF with CREATE2")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -512,6 +513,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||||
)
|
)
|
||||||
if contractCreation {
|
if contractCreation {
|
||||||
ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value)
|
ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value)
|
||||||
|
// Special case for EOF, if the initcode or deployed code is
|
||||||
|
// invalid, the tx is considered valid (so update nonce), but
|
||||||
|
// is to be treated as an exceptional abort (so burn all gas).
|
||||||
|
if errors.Is(vmerr, vm.ErrInvalidEOFInitcode) {
|
||||||
|
st.gasRemaining = 0
|
||||||
|
st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
|
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,3 +116,87 @@ func codeBitmapInternal(code, bits bitvec) bitvec {
|
||||||
}
|
}
|
||||||
return bits
|
return bits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eofCodeBitmap collects data locations in code.
|
||||||
|
func eofCodeBitmap(code []byte) bitvec {
|
||||||
|
// The bitmap is 4 bytes longer than necessary, in case the code
|
||||||
|
// ends with a PUSH32, the algorithm will push zeroes onto the
|
||||||
|
// bitvector outside the bounds of the actual code.
|
||||||
|
bits := make(bitvec, len(code)/8+1+4)
|
||||||
|
return eofCodeBitmapInternal(code, bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eofCodeBitmapInternal is the internal implementation of codeBitmap for EOF
|
||||||
|
// code validation.
|
||||||
|
func eofCodeBitmapInternal(code, bits bitvec) bitvec {
|
||||||
|
for pc := uint64(0); pc < uint64(len(code)); {
|
||||||
|
var (
|
||||||
|
op = OpCode(code[pc])
|
||||||
|
numbits uint8
|
||||||
|
)
|
||||||
|
pc++
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case op >= PUSH1 && op <= PUSH32:
|
||||||
|
numbits = uint8(op - PUSH1 + 1)
|
||||||
|
case op == RJUMP || op == RJUMPI || op == CALLF:
|
||||||
|
numbits = 2
|
||||||
|
case op == RJUMPV:
|
||||||
|
// RJUMPV is unique as it has a variable sized operand.
|
||||||
|
// The total size is determined by the count byte which
|
||||||
|
// immediate proceeds RJUMPV. Truncation will be caught
|
||||||
|
// in other validation steps -- for now, just return a
|
||||||
|
// valid bitmap for as much of the code as is
|
||||||
|
// available.
|
||||||
|
end := uint64(len(code))
|
||||||
|
if pc >= end {
|
||||||
|
// Count missing, no more bits to mark.
|
||||||
|
return bits
|
||||||
|
}
|
||||||
|
numbits = code[pc]*2 + 1
|
||||||
|
if pc+uint64(numbits) > end {
|
||||||
|
// Jump table is truncated, mark as many bits
|
||||||
|
// as possible.
|
||||||
|
numbits = uint8(end - pc)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Op had no immediate operand, continue.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if numbits >= 8 {
|
||||||
|
for ; numbits >= 16; numbits -= 16 {
|
||||||
|
bits.set16(pc)
|
||||||
|
pc += 16
|
||||||
|
}
|
||||||
|
for ; numbits >= 8; numbits -= 8 {
|
||||||
|
bits.set8(pc)
|
||||||
|
pc += 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch numbits {
|
||||||
|
case 1:
|
||||||
|
bits.set1(pc)
|
||||||
|
pc += 1
|
||||||
|
case 2:
|
||||||
|
bits.setN(set2BitsMask, pc)
|
||||||
|
pc += 2
|
||||||
|
case 3:
|
||||||
|
bits.setN(set3BitsMask, pc)
|
||||||
|
pc += 3
|
||||||
|
case 4:
|
||||||
|
bits.setN(set4BitsMask, pc)
|
||||||
|
pc += 4
|
||||||
|
case 5:
|
||||||
|
bits.setN(set5BitsMask, pc)
|
||||||
|
pc += 5
|
||||||
|
case 6:
|
||||||
|
bits.setN(set6BitsMask, pc)
|
||||||
|
pc += 6
|
||||||
|
case 7:
|
||||||
|
bits.setN(set7BitsMask, pc)
|
||||||
|
pc += 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bits
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -56,6 +57,28 @@ func TestJumpDestAnalysis(t *testing.T) {
|
||||||
if ret[test.which] != test.exp {
|
if ret[test.which] != test.exp {
|
||||||
t.Fatalf("test %d: expected %x, got %02x", i, test.exp, ret[test.which])
|
t.Fatalf("test %d: expected %x, got %02x", i, test.exp, ret[test.which])
|
||||||
}
|
}
|
||||||
|
ret = eofCodeBitmap(test.code)
|
||||||
|
if ret[test.which] != test.exp {
|
||||||
|
t.Fatalf("eof test %d: expected %x, got %02x", i, test.exp, ret[test.which])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEOFAnalysis(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
code []byte
|
||||||
|
exp byte
|
||||||
|
which int
|
||||||
|
}{
|
||||||
|
{[]byte{byte(RJUMP), 0x01, 0x01, 0x01}, 0b0000_0110, 0},
|
||||||
|
{[]byte{byte(RJUMPI), byte(RJUMP), byte(RJUMP), byte(RJUMPI)}, 0b0011_0110, 0},
|
||||||
|
{[]byte{byte(RJUMPV), 0x02, byte(RJUMP), 0x00, byte(RJUMPI), 0x00}, 0b0011_1110, 0},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
ret := eofCodeBitmap(test.code)
|
||||||
|
if ret[test.which] != test.exp {
|
||||||
|
t.Fatalf("test %d: expected %x, got %02x", i, test.exp, ret[test.which])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,3 +128,20 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) {
|
||||||
op = STOP
|
op = STOP
|
||||||
bench.Run(op.String(), bencher)
|
bench.Run(op.String(), bencher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCodeAnalysis(t *testing.T) {
|
||||||
|
for _, tc := range []string{
|
||||||
|
"5e30303030",
|
||||||
|
} {
|
||||||
|
eofCodeBitmap(common.FromHex(tc))
|
||||||
|
codeBitmap(common.FromHex(tc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzCodeAnalysis(f *testing.F) {
|
||||||
|
f.Add(common.FromHex("5e30303030"))
|
||||||
|
f.Fuzz(func(t *testing.T, data []byte) {
|
||||||
|
eofCodeBitmap(data)
|
||||||
|
codeBitmap(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ type Contract struct {
|
||||||
analysis bitvec // Locally cached result of JUMPDEST analysis
|
analysis bitvec // Locally cached result of JUMPDEST analysis
|
||||||
|
|
||||||
Code []byte
|
Code []byte
|
||||||
|
Container *Container
|
||||||
CodeHash common.Hash
|
CodeHash common.Hash
|
||||||
CodeAddr *common.Address
|
CodeAddr *common.Address
|
||||||
Input []byte
|
Input []byte
|
||||||
|
|
@ -144,11 +145,12 @@ func (c *Contract) AsDelegate() *Contract {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOp returns the n'th element in the contract's byte array
|
// GetOp returns the n'th element in the contract's byte array
|
||||||
func (c *Contract) GetOp(n uint64) OpCode {
|
func (c *Contract) GetOp(n uint64, s uint64) OpCode {
|
||||||
if n < uint64(len(c.Code)) {
|
if c.IsEOF() && n < uint64(len(c.Container.Code[s])) {
|
||||||
|
return OpCode(c.Container.Code[s][n])
|
||||||
|
} else if n < uint64(len(c.Code)) {
|
||||||
return OpCode(c.Code[n])
|
return OpCode(c.Code[n])
|
||||||
}
|
}
|
||||||
|
|
||||||
return STOP
|
return STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,10 +195,23 @@ func (c *Contract) Value() *uint256.Int {
|
||||||
return c.value
|
return c.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsEOF returns whether the contract is EOF.
|
||||||
|
func (c *Contract) IsEOF() bool {
|
||||||
|
return c.Container != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Contract) CodeAt(section uint64) []byte {
|
||||||
|
if c.Container == nil {
|
||||||
|
return c.Code
|
||||||
|
}
|
||||||
|
return c.Container.Code[section]
|
||||||
|
}
|
||||||
|
|
||||||
// SetCallCode sets the code of the contract and address of the backing data
|
// SetCallCode sets the code of the contract and address of the backing data
|
||||||
// object
|
// object
|
||||||
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) {
|
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte, container *Container) {
|
||||||
c.Code = code
|
c.Code = code
|
||||||
|
c.Container = container
|
||||||
c.CodeHash = hash
|
c.CodeHash = hash
|
||||||
c.CodeAddr = addr
|
c.CodeAddr = addr
|
||||||
}
|
}
|
||||||
|
|
|
||||||
134
core/vm/eips.go
134
core/vm/eips.go
|
|
@ -21,6 +21,8 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -557,3 +559,135 @@ func enable7702(jt *JumpTable) {
|
||||||
jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929
|
jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929
|
||||||
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702
|
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enableEOF applies the EOF changes.
|
||||||
|
func enableEOF(jt *JumpTable) {
|
||||||
|
// Deprecate opcodes
|
||||||
|
undefined := &operation{
|
||||||
|
execute: opUndefined,
|
||||||
|
constantGas: 0,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
undefined: true,
|
||||||
|
}
|
||||||
|
jt[CALLCODE] = undefined
|
||||||
|
jt[SELFDESTRUCT] = undefined
|
||||||
|
jt[JUMP] = undefined
|
||||||
|
jt[JUMPI] = undefined
|
||||||
|
jt[PC] = undefined
|
||||||
|
|
||||||
|
// New opcodes
|
||||||
|
jt[RJUMP] = &operation{
|
||||||
|
execute: opRjump,
|
||||||
|
constantGas: GasQuickStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
terminal: true,
|
||||||
|
}
|
||||||
|
jt[RJUMPI] = &operation{
|
||||||
|
execute: opRjumpi,
|
||||||
|
constantGas: GasFastishStep,
|
||||||
|
minStack: minStack(1, 0),
|
||||||
|
maxStack: maxStack(1, 0),
|
||||||
|
}
|
||||||
|
jt[RJUMPV] = &operation{
|
||||||
|
execute: opRjumpv,
|
||||||
|
constantGas: GasFastishStep,
|
||||||
|
minStack: minStack(1, 0),
|
||||||
|
maxStack: maxStack(1, 0),
|
||||||
|
}
|
||||||
|
jt[CALLF] = &operation{
|
||||||
|
execute: opCallf,
|
||||||
|
constantGas: GasFastStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
}
|
||||||
|
jt[RETF] = &operation{
|
||||||
|
execute: opRetf,
|
||||||
|
constantGas: GasFastishStep,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
terminal: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// opRjump implements the rjump opcode.
|
||||||
|
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||||
|
offset = parseInt16(code[*pc+1:])
|
||||||
|
)
|
||||||
|
// move pc past op and operand (+3), add relative offset, subtract 1 to
|
||||||
|
// account for interpreter loop.
|
||||||
|
*pc = uint64(int64(*pc+3) + int64(offset) - 1)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// opRjumpi implements the RJUMPI opcode
|
||||||
|
func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
condition := scope.Stack.pop()
|
||||||
|
if condition.BitLen() == 0 {
|
||||||
|
// Not branching, just skip over immediate argument.
|
||||||
|
*pc += 2
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return opRjump(pc, interpreter, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
// opRjumpv implements the RJUMPV opcode
|
||||||
|
func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||||
|
count = uint64(code[*pc+1])
|
||||||
|
idx = scope.Stack.pop()
|
||||||
|
)
|
||||||
|
if idx, overflow := idx.Uint64WithOverflow(); overflow || idx >= count {
|
||||||
|
// Index out-of-bounds, don't branch, just skip over immediate
|
||||||
|
// argument.
|
||||||
|
*pc += 1 + count*2
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
offset := parseInt16(code[*pc+2+2*idx.Uint64():])
|
||||||
|
*pc = uint64(int64(*pc+2+count*2) + int64(offset) - 1)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// opCallf implements the CALLF opcode
|
||||||
|
func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||||
|
idx = binary.BigEndian.Uint16(code[*pc+1:])
|
||||||
|
typ = scope.Contract.Container.Types[scope.CodeSection]
|
||||||
|
)
|
||||||
|
if scope.Stack.len()+int(typ.MaxStackHeight) >= 1024 {
|
||||||
|
return nil, fmt.Errorf("stack overflow")
|
||||||
|
}
|
||||||
|
retCtx := &ReturnContext{
|
||||||
|
Section: scope.CodeSection,
|
||||||
|
Pc: *pc + 3,
|
||||||
|
StackHeight: scope.Stack.len() - int(typ.Input),
|
||||||
|
}
|
||||||
|
scope.ReturnStack = append(scope.ReturnStack, retCtx)
|
||||||
|
scope.CodeSection = uint64(idx)
|
||||||
|
*pc = 0
|
||||||
|
*pc -= 1 // hacks xD
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// opRetf implements the RETF opcode
|
||||||
|
func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
last = len(scope.ReturnStack) - 1
|
||||||
|
retCtx = scope.ReturnStack[last]
|
||||||
|
)
|
||||||
|
scope.ReturnStack = scope.ReturnStack[:last]
|
||||||
|
scope.CodeSection = retCtx.Section
|
||||||
|
*pc = retCtx.Pc - 1
|
||||||
|
|
||||||
|
// If returning from top frame, exit cleanly.
|
||||||
|
if len(scope.ReturnStack) == 0 {
|
||||||
|
return nil, errStopToken
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
>>>>>>> 3532f16eb7 (core/vm: add eof container)
|
||||||
|
}
|
||||||
|
|
|
||||||
315
core/vm/eof.go
Normal file
315
core/vm/eof.go
Normal file
|
|
@ -0,0 +1,315 @@
|
||||||
|
// Copyright 2022 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/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
offsetVersion = 2
|
||||||
|
offsetTypesKind = 3
|
||||||
|
offsetCodeKind = 6
|
||||||
|
|
||||||
|
kindTypes = 1
|
||||||
|
kindCode = 2
|
||||||
|
kindData = 3
|
||||||
|
|
||||||
|
eofFormatByte = 0xef
|
||||||
|
eof1Version = 1
|
||||||
|
|
||||||
|
maxInputItems = 127
|
||||||
|
maxOutputItems = 127
|
||||||
|
maxStackHeight = 1023
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidMagic = errors.New("invalid magic")
|
||||||
|
ErrInvalidVersion = errors.New("invalid version")
|
||||||
|
ErrMissingTypeHeader = errors.New("missing type header")
|
||||||
|
ErrInvalidTypeSize = errors.New("invalid type section size")
|
||||||
|
ErrMissingCodeHeader = errors.New("missing code header")
|
||||||
|
ErrInvalidCodeHeader = errors.New("invalid code header")
|
||||||
|
ErrInvalidCodeSize = errors.New("invalid code size")
|
||||||
|
ErrMissingDataHeader = errors.New("missing data header")
|
||||||
|
ErrMissingTerminator = errors.New("missing header terminator")
|
||||||
|
ErrTooManyInputs = errors.New("invalid type content, too many inputs")
|
||||||
|
ErrTooManyOutputs = errors.New("invalid type content, too many inputs")
|
||||||
|
ErrInvalidSection0Type = errors.New("invalid section 0 type, input and output should be zero")
|
||||||
|
ErrTooLargeMaxStackHeight = errors.New("invalid type content, max stack height exceeds limit")
|
||||||
|
ErrInvalidContainerSize = errors.New("invalid container size")
|
||||||
|
)
|
||||||
|
|
||||||
|
var eofMagic = []byte{0xef, 0x00}
|
||||||
|
|
||||||
|
// HasEOFByte returns true if code starts with 0xEF byte
|
||||||
|
func HasEOFByte(code []byte) bool {
|
||||||
|
return len(code) != 0 && code[0] == eofFormatByte
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasEOFMagic returns true if code starts with magic defined by EIP-3540
|
||||||
|
func hasEOFMagic(code []byte) bool {
|
||||||
|
return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)])
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEOFVersion1 returns true if the code's version byte equals eof1Version. It
|
||||||
|
// does not verify the EOF magic is valid.
|
||||||
|
func isEOFVersion1(code []byte) bool {
|
||||||
|
return 2 < len(code) && code[2] == byte(eof1Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container is an EOF container object.
|
||||||
|
type Container struct {
|
||||||
|
Types []*FunctionMetadata
|
||||||
|
Code [][]byte
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// FunctionMetadata is an EOF function signature.
|
||||||
|
type FunctionMetadata struct {
|
||||||
|
Input uint8
|
||||||
|
Output uint8
|
||||||
|
MaxStackHeight uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary encodes an EOF container into binary format.
|
||||||
|
func (c *Container) MarshalBinary() []byte {
|
||||||
|
// Build EOF prefix.
|
||||||
|
b := make([]byte, 2)
|
||||||
|
copy(b, eofMagic)
|
||||||
|
b = append(b, eof1Version)
|
||||||
|
|
||||||
|
// Write section headers.
|
||||||
|
b = append(b, kindTypes)
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(len(c.Types)*4))
|
||||||
|
b = append(b, kindCode)
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(len(c.Code)))
|
||||||
|
for _, code := range c.Code {
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(len(code)))
|
||||||
|
}
|
||||||
|
b = append(b, kindData)
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(len(c.Data)))
|
||||||
|
b = append(b, 0) // terminator
|
||||||
|
|
||||||
|
// Write section contents.
|
||||||
|
for _, ty := range c.Types {
|
||||||
|
b = append(b, []byte{ty.Input, ty.Output, byte(ty.MaxStackHeight >> 8), byte(ty.MaxStackHeight & 0x00ff)}...)
|
||||||
|
}
|
||||||
|
for _, code := range c.Code {
|
||||||
|
b = append(b, code...)
|
||||||
|
}
|
||||||
|
b = append(b, c.Data...)
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary decodes an EOF container.
|
||||||
|
func (c *Container) UnmarshalBinary(b []byte) error {
|
||||||
|
if !hasEOFMagic(b) {
|
||||||
|
return fmt.Errorf("%w: want %x", ErrInvalidMagic, eofMagic)
|
||||||
|
}
|
||||||
|
if len(b) < 14 {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if !isEOFVersion1(b) {
|
||||||
|
return fmt.Errorf("%w: have %d, want %d", ErrInvalidVersion, b[2], eof1Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
kind, typesSize, dataSize int
|
||||||
|
codeSizes []int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse type section header.
|
||||||
|
kind, typesSize, err = parseSection(b, offsetTypesKind)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if kind != kindTypes {
|
||||||
|
return fmt.Errorf("%w: found section kind %x instead", ErrMissingTypeHeader, kind)
|
||||||
|
}
|
||||||
|
if typesSize < 4 || typesSize%4 != 0 {
|
||||||
|
return fmt.Errorf("%w: type section size must be divisible by 4, have %d", ErrInvalidTypeSize, typesSize)
|
||||||
|
}
|
||||||
|
if typesSize/4 > 1024 {
|
||||||
|
return fmt.Errorf("%w: type section must not exceed 4*1024, have %d", ErrInvalidTypeSize, typesSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse code section header.
|
||||||
|
kind, codeSizes, err = parseSectionList(b, offsetCodeKind)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if kind != kindCode {
|
||||||
|
return fmt.Errorf("%w: found section kind %x instead", ErrMissingCodeHeader, kind)
|
||||||
|
}
|
||||||
|
if len(codeSizes) != typesSize/4 {
|
||||||
|
return fmt.Errorf("%w: mismatch of code sections cound and type signatures, types %d, code %d", ErrInvalidCodeSize, typesSize/4, len(codeSizes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse data section header.
|
||||||
|
offsetDataKind := offsetCodeKind + 2 + 2*len(codeSizes) + 1
|
||||||
|
kind, dataSize, err = parseSection(b, offsetDataKind)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if kind != kindData {
|
||||||
|
return fmt.Errorf("%w: found section %x instead", ErrMissingDataHeader, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for terminator.
|
||||||
|
offsetTerminator := offsetDataKind + 3
|
||||||
|
if len(b) < offsetTerminator {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if b[offsetTerminator] != 0 {
|
||||||
|
return fmt.Errorf("%w: have %x", ErrMissingTerminator, b[offsetTerminator])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify overall container size.
|
||||||
|
expectedSize := offsetTerminator + typesSize + sum(codeSizes) + dataSize + 1
|
||||||
|
if len(b) != expectedSize {
|
||||||
|
return fmt.Errorf("%w: have %d, want %d", ErrInvalidContainerSize, len(b), expectedSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse types section.
|
||||||
|
idx := offsetTerminator + 1
|
||||||
|
var types []*FunctionMetadata
|
||||||
|
for i := 0; i < typesSize/4; i++ {
|
||||||
|
sig := &FunctionMetadata{
|
||||||
|
Input: b[idx+i*4],
|
||||||
|
Output: b[idx+i*4+1],
|
||||||
|
MaxStackHeight: binary.BigEndian.Uint16(b[idx+i*4+2:]),
|
||||||
|
}
|
||||||
|
if sig.Input > maxInputItems {
|
||||||
|
return fmt.Errorf("%w for section %d: have %d", ErrTooManyInputs, i, sig.Input)
|
||||||
|
}
|
||||||
|
if sig.Output > maxOutputItems {
|
||||||
|
return fmt.Errorf("%w for section %d: have %d", ErrTooManyOutputs, i, sig.Output)
|
||||||
|
}
|
||||||
|
if sig.MaxStackHeight > maxStackHeight {
|
||||||
|
return fmt.Errorf("%w for section %d: have %d", ErrTooLargeMaxStackHeight, i, sig.MaxStackHeight)
|
||||||
|
}
|
||||||
|
types = append(types, sig)
|
||||||
|
}
|
||||||
|
if types[0].Input != 0 || types[0].Output != 0 {
|
||||||
|
return fmt.Errorf("%w: have %d, %d", ErrInvalidSection0Type, types[0].Input, types[0].Output)
|
||||||
|
}
|
||||||
|
c.Types = types
|
||||||
|
|
||||||
|
// Parse code sections.
|
||||||
|
idx += typesSize
|
||||||
|
code := make([][]byte, len(codeSizes))
|
||||||
|
for i, size := range codeSizes {
|
||||||
|
if size == 0 {
|
||||||
|
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidCodeSize, i)
|
||||||
|
}
|
||||||
|
code[i] = b[idx : idx+size]
|
||||||
|
idx += size
|
||||||
|
}
|
||||||
|
c.Code = code
|
||||||
|
|
||||||
|
// Parse data section.
|
||||||
|
c.Data = b[idx : idx+dataSize]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCode validates each code section of the container against the EOF v1
|
||||||
|
// rule set.
|
||||||
|
func (c *Container) ValidateCode(jt *JumpTable) error {
|
||||||
|
for i, code := range c.Code {
|
||||||
|
if err := validateCode(code, i, c.Types, jt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSection decodes a (kind, size) pair from an EOF header.
|
||||||
|
func parseSection(b []byte, idx int) (kind, size int, err error) {
|
||||||
|
if idx+3 >= len(b) {
|
||||||
|
return 0, 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
kind = int(b[idx])
|
||||||
|
size = int(binary.BigEndian.Uint16(b[idx+1:]))
|
||||||
|
return kind, size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSectionList decodes a (kind, len, []codeSize) section list from an EOF
|
||||||
|
// header.
|
||||||
|
func parseSectionList(b []byte, idx int) (kind int, list []int, err error) {
|
||||||
|
if idx >= len(b) {
|
||||||
|
return 0, nil, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
kind = int(b[idx])
|
||||||
|
list, err = parseList(b, idx+1)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
return kind, list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseList decodes a list of uint16..
|
||||||
|
func parseList(b []byte, idx int) ([]int, error) {
|
||||||
|
if len(b) < idx+2 {
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
count := binary.BigEndian.Uint16(b[idx:])
|
||||||
|
if len(b) <= idx+2+int(count)*2 {
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
list := make([]int, count)
|
||||||
|
for i := 0; i < int(count); i++ {
|
||||||
|
list[i] = int(binary.BigEndian.Uint16(b[idx+2+2*i:]))
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseUint16 parses a 16 bit unsigned integer.
|
||||||
|
func parseUint16(b []byte) (int, error) {
|
||||||
|
if len(b) < 2 {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return int(binary.BigEndian.Uint16(b)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseInt16 parses a 16 bit signed integer.
|
||||||
|
func parseInt16(b []byte) int {
|
||||||
|
return int(int16(b[1]) | int16(b[0])<<8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// max returns the maximum of a and b.
|
||||||
|
func max(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// sum computes the sum of a slice.
|
||||||
|
func sum(list []int) (s int) {
|
||||||
|
for _, n := range list {
|
||||||
|
s += n
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
66
core/vm/eof_test.go
Normal file
66
core/vm/eof_test.go
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright 2022 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 (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEOFMarshaling(t *testing.T) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
want Container
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
want: Container{
|
||||||
|
Types: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
|
Code: [][]byte{common.Hex2Bytes("604200")},
|
||||||
|
Data: []byte{0x01, 0x02, 0x03},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
want: Container{
|
||||||
|
Types: []*FunctionMetadata{
|
||||||
|
{Input: 0, Output: 0, MaxStackHeight: 1},
|
||||||
|
{Input: 2, Output: 3, MaxStackHeight: 4},
|
||||||
|
{Input: 1, Output: 1, MaxStackHeight: 1},
|
||||||
|
},
|
||||||
|
Code: [][]byte{
|
||||||
|
common.Hex2Bytes("604200"),
|
||||||
|
common.Hex2Bytes("6042604200"),
|
||||||
|
common.Hex2Bytes("00"),
|
||||||
|
},
|
||||||
|
Data: []byte{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
var (
|
||||||
|
b = test.want.MarshalBinary()
|
||||||
|
got Container
|
||||||
|
)
|
||||||
|
t.Logf("b: %#x", b)
|
||||||
|
if err := got.UnmarshalBinary(b); err != nil && err != test.err {
|
||||||
|
t.Fatalf("test %d: got error \"%v\", want \"%v\"", i, err, test.err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, test.want) {
|
||||||
|
t.Fatalf("test %d: got %+v, want %+v", i, got, test.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,7 +36,10 @@ var (
|
||||||
ErrWriteProtection = errors.New("write protection")
|
ErrWriteProtection = errors.New("write protection")
|
||||||
ErrReturnDataOutOfBounds = errors.New("return data out of bounds")
|
ErrReturnDataOutOfBounds = errors.New("return data out of bounds")
|
||||||
ErrGasUintOverflow = errors.New("gas uint64 overflow")
|
ErrGasUintOverflow = errors.New("gas uint64 overflow")
|
||||||
|
ErrLegacyCode = errors.New("invalid code: EOF contract must not deploy legacy code")
|
||||||
ErrInvalidCode = errors.New("invalid code: must not begin with 0xef")
|
ErrInvalidCode = errors.New("invalid code: must not begin with 0xef")
|
||||||
|
ErrInvalidEOF = errors.New("invalid eof")
|
||||||
|
ErrInvalidEOFInitcode = errors.New("invalid eof initcode")
|
||||||
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
|
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
|
||||||
|
|
||||||
// errStopToken is an internal token indicating interpreter loop termination,
|
// errStopToken is an internal token indicating interpreter loop termination,
|
||||||
|
|
@ -51,10 +54,14 @@ type ErrStackUnderflow struct {
|
||||||
required int
|
required int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ErrStackUnderflow) Error() string {
|
func (e ErrStackUnderflow) Error() string {
|
||||||
return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required)
|
return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e ErrStackUnderflow) Unwrap() error {
|
||||||
|
return fmt.Errorf("stack underflow")
|
||||||
|
}
|
||||||
|
|
||||||
// ErrStackOverflow wraps an evm error when the items on the stack exceeds
|
// ErrStackOverflow wraps an evm error when the items on the stack exceeds
|
||||||
// the maximum allowance.
|
// the maximum allowance.
|
||||||
type ErrStackOverflow struct {
|
type ErrStackOverflow struct {
|
||||||
|
|
@ -62,10 +69,14 @@ type ErrStackOverflow struct {
|
||||||
limit int
|
limit int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ErrStackOverflow) Error() string {
|
func (e ErrStackOverflow) Error() string {
|
||||||
return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit)
|
return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e ErrStackOverflow) Unwrap() error {
|
||||||
|
return fmt.Errorf("stack overflow")
|
||||||
|
}
|
||||||
|
|
||||||
// ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered.
|
// ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered.
|
||||||
type ErrInvalidOpCode struct {
|
type ErrInvalidOpCode struct {
|
||||||
opcode OpCode
|
opcode OpCode
|
||||||
|
|
|
||||||
178
core/vm/evm.go
178
core/vm/evm.go
|
|
@ -18,7 +18,9 @@ package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -242,7 +244,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
// If the account has no code, we can abort here
|
// If the account has no code, we can abort here
|
||||||
// The depth-check is already done, and precompiles handled above
|
// The depth-check is already done, and precompiles handled above
|
||||||
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
|
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
|
||||||
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code)
|
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code))
|
||||||
ret, err = evm.interpreter.Run(contract, input, false)
|
ret, err = evm.interpreter.Run(contract, input, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
|
|
@ -299,6 +301,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
||||||
} else {
|
} else {
|
||||||
addrCopy := addr
|
addrCopy := addr
|
||||||
|
code := evm.StateDB.GetCode(addrCopy)
|
||||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
// 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.
|
// The contract is a scoped environment for this execution context only.
|
||||||
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
|
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
|
||||||
|
|
@ -306,7 +309,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
witness.AddCode(evm.StateDB.GetCode(addrCopy))
|
witness.AddCode(evm.StateDB.GetCode(addrCopy))
|
||||||
witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
|
witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
|
||||||
}
|
}
|
||||||
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy))
|
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code))
|
||||||
ret, err = evm.interpreter.Run(contract, input, false)
|
ret, err = evm.interpreter.Run(contract, input, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
|
|
@ -351,13 +354,14 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||||
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
||||||
} else {
|
} else {
|
||||||
addrCopy := addr
|
addrCopy := addr
|
||||||
|
code := evm.StateDB.GetCode(addrCopy)
|
||||||
// Initialise a new contract and make initialise the delegate values
|
// Initialise a new contract and make initialise the delegate values
|
||||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
||||||
if witness := evm.StateDB.Witness(); witness != nil {
|
if witness := evm.StateDB.Witness(); witness != nil {
|
||||||
witness.AddCode(evm.StateDB.GetCode(addrCopy))
|
witness.AddCode(evm.StateDB.GetCode(addrCopy))
|
||||||
witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
|
witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
|
||||||
}
|
}
|
||||||
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy))
|
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code))
|
||||||
ret, err = evm.interpreter.Run(contract, input, false)
|
ret, err = evm.interpreter.Run(contract, input, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
|
|
@ -411,12 +415,13 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
addrCopy := addr
|
addrCopy := addr
|
||||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
// 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.
|
// The contract is a scoped environment for this execution context only.
|
||||||
|
code := evm.StateDB.GetCode(addrCopy)
|
||||||
contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas)
|
contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas)
|
||||||
if witness := evm.StateDB.Witness(); witness != nil {
|
if witness := evm.StateDB.Witness(); witness != nil {
|
||||||
witness.AddCode(evm.StateDB.GetCode(addrCopy))
|
witness.AddCode(evm.StateDB.GetCode(addrCopy))
|
||||||
witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
|
witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
|
||||||
}
|
}
|
||||||
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy))
|
contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code))
|
||||||
// When an error was returned by the EVM or when setting the creation code
|
// 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
|
// 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.
|
// when we're in Homestead this also counts for code storage gas errors.
|
||||||
|
|
@ -449,7 +454,7 @@ func (c *codeAndHash) Hash() common.Hash {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create creates a new contract using code as deployment code.
|
// create creates a new contract using code as deployment code.
|
||||||
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) {
|
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode, fromEOF bool) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) {
|
||||||
if evm.Config.Tracer != nil {
|
if evm.Config.Tracer != nil {
|
||||||
evm.captureBegin(evm.depth, typ, caller.Address(), address, codeAndHash.code, gas, value.ToBig())
|
evm.captureBegin(evm.depth, typ, caller.Address(), address, codeAndHash.code, gas, value.ToBig())
|
||||||
defer func(startGas uint64) {
|
defer func(startGas uint64) {
|
||||||
|
|
@ -464,6 +469,33 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||||
return nil, common.Address{}, gas, ErrInsufficientBalance
|
return nil, common.Address{}, gas, ErrInsufficientBalance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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. If
|
||||||
|
// the initcode is EOF, contract.Container will be set.
|
||||||
|
contract := NewContract(caller, AccountRef(address), value, gas)
|
||||||
|
contract.SetCodeOptionalHash(&address, codeAndHash)
|
||||||
|
contract.IsDeployment = true
|
||||||
|
|
||||||
|
// Validate initcode per EOF rules. If caller is EOF and initcode is legacy, fail.
|
||||||
|
isInitcodeEOF := hasEOFMagic(codeAndHash.code)
|
||||||
|
if evm.chainRules.IsShanghai {
|
||||||
|
if isInitcodeEOF {
|
||||||
|
// If the initcode is EOF, verify it is well-formed.
|
||||||
|
var c Container
|
||||||
|
if err := c.UnmarshalBinary(codeAndHash.code); err != nil {
|
||||||
|
return nil, common.Address{}, gas, fmt.Errorf("%w: %v", ErrInvalidEOFInitcode, err)
|
||||||
|
}
|
||||||
|
if err := c.ValidateCode(evm.interpreter.tableEOF); err != nil {
|
||||||
|
return nil, common.Address{}, gas, fmt.Errorf("%w: %v", ErrInvalidEOFInitcode, err)
|
||||||
|
}
|
||||||
|
contract.Container = &c
|
||||||
|
} else if fromEOF {
|
||||||
|
// Don't allow EOF contract to execute legacy initcode.
|
||||||
|
return nil, common.Address{}, gas, ErrLegacyCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for nonce overflow and then update caller nonce by 1.
|
||||||
nonce := evm.StateDB.GetNonce(caller.Address())
|
nonce := evm.StateDB.GetNonce(caller.Address())
|
||||||
if nonce+1 < nonce {
|
if nonce+1 < nonce {
|
||||||
return nil, common.Address{}, gas, ErrNonceUintOverflow
|
return nil, common.Address{}, gas, ErrNonceUintOverflow
|
||||||
|
|
@ -508,13 +540,71 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
}
|
}
|
||||||
evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)
|
evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)
|
||||||
|
|
||||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
// Charge the contract creation init gas in verkle mode
|
||||||
// The contract is a scoped environment for this execution context only.
|
if evm.chainRules.IsEIP4762 {
|
||||||
contract := NewContract(caller, AccountRef(address), value, gas)
|
if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) {
|
||||||
contract.SetCodeOptionalHash(&address, codeAndHash)
|
err = ErrOutOfGas
|
||||||
contract.IsDeployment = true
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ret, err = evm.initNewContract(contract, address, value)
|
if err == nil {
|
||||||
|
ret, err = evm.interpreter.Run(contract, nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the max code size has been exceeded, assign err if the case.
|
||||||
|
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
|
||||||
|
err = ErrMaxCodeSizeExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject legacy contract deployment from EOF.
|
||||||
|
if err == nil && isInitcodeEOF && !hasEOFMagic(ret) {
|
||||||
|
err = ErrLegacyCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject code starting with 0xEF if EIP-3541 is enabled.
|
||||||
|
if err == nil && len(ret) >= 1 && HasEOFByte(ret) {
|
||||||
|
if evm.chainRules.IsShanghai {
|
||||||
|
var c Container
|
||||||
|
if err = c.UnmarshalBinary(ret); err == nil {
|
||||||
|
err = c.ValidateCode(evm.interpreter.tableEOF)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("%w: %v", ErrInvalidEOF, err)
|
||||||
|
}
|
||||||
|
} else if evm.chainRules.IsLondon {
|
||||||
|
err = ErrInvalidCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the contract creation ran successfully and no errors were returned
|
||||||
|
// calculate the gas required to store the code. If the code could not
|
||||||
|
// be stored due to not enough gas set an error and let it be handled
|
||||||
|
// by the error checking condition below.
|
||||||
|
if err == nil {
|
||||||
|
if !evm.chainRules.IsEIP4762 {
|
||||||
|
createDataGas := uint64(len(ret)) * params.CreateDataGas
|
||||||
|
if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||||
|
err = ErrCodeStoreOutOfGas
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Contract creation completed, touch the missing fields in the contract
|
||||||
|
if !contract.UseGas(evm.AccessEvents.AddAccount(address, true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) {
|
||||||
|
err = ErrCodeStoreOutOfGas
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) {
|
||||||
|
err = ErrCodeStoreOutOfGas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
evm.StateDB.SetCode(address, ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
|
|
@ -524,55 +614,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
return ret, address, contract.Gas, err
|
return ret, address, contract.Gas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// initNewContract runs a new contract's creation code, performs checks on the
|
|
||||||
// resulting code that is to be deployed, and consumes necessary gas.
|
|
||||||
func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int) ([]byte, error) {
|
|
||||||
// Charge the contract creation init gas in verkle mode
|
|
||||||
if evm.chainRules.IsEIP4762 {
|
|
||||||
if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) {
|
|
||||||
return nil, ErrOutOfGas
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret, err := evm.interpreter.Run(contract, nil, false)
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether the max code size has been exceeded, assign err if the case.
|
|
||||||
if evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
|
|
||||||
return ret, ErrMaxCodeSizeExceeded
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reject code starting with 0xEF if EIP-3541 is enabled.
|
|
||||||
if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
|
|
||||||
return ret, ErrInvalidCode
|
|
||||||
}
|
|
||||||
|
|
||||||
if !evm.chainRules.IsEIP4762 {
|
|
||||||
createDataGas := uint64(len(ret)) * params.CreateDataGas
|
|
||||||
if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
|
||||||
return ret, ErrCodeStoreOutOfGas
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Contract creation completed, touch the missing fields in the contract
|
|
||||||
if !contract.UseGas(evm.AccessEvents.AddAccount(address, true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) {
|
|
||||||
return ret, ErrCodeStoreOutOfGas
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) {
|
|
||||||
return ret, ErrCodeStoreOutOfGas
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
evm.StateDB.SetCode(address, ret)
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates a new contract using code as deployment code.
|
// Create creates a new contract using code as deployment code.
|
||||||
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||||
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
|
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
|
||||||
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE)
|
isEOF := hasEOFMagic(evm.StateDB.GetCode(caller.Address()))
|
||||||
|
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE, isEOF)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create2 creates a new contract using code as deployment code.
|
// Create2 creates a new contract using code as deployment code.
|
||||||
|
|
@ -582,7 +628,8 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint2
|
||||||
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||||
codeAndHash := &codeAndHash{code: code}
|
codeAndHash := &codeAndHash{code: code}
|
||||||
contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
|
contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
|
||||||
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2)
|
isEOF := hasEOFMagic(evm.StateDB.GetCode(caller.Address()))
|
||||||
|
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, isEOF)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChainConfig returns the environment's chain configuration
|
// ChainConfig returns the environment's chain configuration
|
||||||
|
|
@ -628,3 +675,18 @@ func (evm *EVM) GetVMContext() *tracing.VMContext {
|
||||||
StateDB: evm.StateDB,
|
StateDB: evm.StateDB,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseContainer tries to parse an EOF container if the Shanghai fork is active. It expects the code to already be validated.
|
||||||
|
func (evm *EVM) parseContainer(b []byte) *Container {
|
||||||
|
if evm.chainRules.IsShanghai {
|
||||||
|
var c Container
|
||||||
|
if err := c.UnmarshalBinary(b); err != nil && strings.HasPrefix(err.Error(), "invalid magic") {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
// Code was already validated, so no other errors should be possible.
|
||||||
|
panic(fmt.Sprintf("unexpected error: %v\ncode: %s\n", err, common.Bytes2Hex(b)))
|
||||||
|
}
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
const (
|
const (
|
||||||
GasQuickStep uint64 = 2
|
GasQuickStep uint64 = 2
|
||||||
GasFastestStep uint64 = 3
|
GasFastestStep uint64 = 3
|
||||||
|
GasFastishStep uint64 = 4
|
||||||
GasFastStep uint64 = 5
|
GasFastStep uint64 = 5
|
||||||
GasMidStep uint64 = 8
|
GasMidStep uint64 = 8
|
||||||
GasSlowStep uint64 = 10
|
GasSlowStep uint64 = 10
|
||||||
|
|
|
||||||
|
|
@ -969,12 +969,13 @@ func makeLog(size int) executionFunc {
|
||||||
// opPush1 is a specialized version of pushN
|
// opPush1 is a specialized version of pushN
|
||||||
func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
var (
|
var (
|
||||||
codeLen = uint64(len(scope.Contract.Code))
|
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||||
|
codeLen = uint64(len(code))
|
||||||
integer = new(uint256.Int)
|
integer = new(uint256.Int)
|
||||||
)
|
)
|
||||||
*pc += 1
|
*pc += 1
|
||||||
if *pc < codeLen {
|
if *pc < codeLen {
|
||||||
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
|
scope.Stack.push(integer.SetUint64(uint64(code[*pc])))
|
||||||
} else {
|
} else {
|
||||||
scope.Stack.push(integer.Clear())
|
scope.Stack.push(integer.Clear())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu
|
||||||
expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected))
|
expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected))
|
||||||
stack.push(x)
|
stack.push(x)
|
||||||
stack.push(y)
|
stack.push(y)
|
||||||
opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
|
opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
||||||
if len(stack.data) != 1 {
|
if len(stack.data) != 1 {
|
||||||
t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data))
|
t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data))
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +232,7 @@ func TestAddMod(t *testing.T) {
|
||||||
stack.push(z)
|
stack.push(z)
|
||||||
stack.push(y)
|
stack.push(y)
|
||||||
stack.push(x)
|
stack.push(x)
|
||||||
opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
|
opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
||||||
actual := stack.pop()
|
actual := stack.pop()
|
||||||
if actual.Cmp(expected) != 0 {
|
if actual.Cmp(expected) != 0 {
|
||||||
t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual)
|
t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual)
|
||||||
|
|
@ -259,7 +259,7 @@ func TestWriteExpectedValues(t *testing.T) {
|
||||||
y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
|
y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
|
||||||
stack.push(x)
|
stack.push(x)
|
||||||
stack.push(y)
|
stack.push(y)
|
||||||
opFn(&pc, interpreter, &ScopeContext{nil, stack, nil})
|
opFn(&pc, interpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
||||||
actual := stack.pop()
|
actual := stack.pop()
|
||||||
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
|
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
|
||||||
}
|
}
|
||||||
|
|
@ -295,7 +295,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
|
||||||
var (
|
var (
|
||||||
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
|
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
|
||||||
stack = newstack()
|
stack = newstack()
|
||||||
scope = &ScopeContext{nil, stack, nil}
|
scope = &ScopeContext{nil, stack, nil, 0, nil}
|
||||||
evmInterpreter = NewEVMInterpreter(env)
|
evmInterpreter = NewEVMInterpreter(env)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -546,13 +546,13 @@ func TestOpMstore(t *testing.T) {
|
||||||
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
|
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
|
||||||
stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v)))
|
stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v)))
|
||||||
stack.push(new(uint256.Int))
|
stack.push(new(uint256.Int))
|
||||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil})
|
||||||
if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
|
if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
|
||||||
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
|
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
|
||||||
}
|
}
|
||||||
stack.push(new(uint256.Int).SetUint64(0x1))
|
stack.push(new(uint256.Int).SetUint64(0x1))
|
||||||
stack.push(new(uint256.Int))
|
stack.push(new(uint256.Int))
|
||||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil})
|
||||||
if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
|
if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
|
||||||
t.Fatalf("Mstore failed to overwrite previous value")
|
t.Fatalf("Mstore failed to overwrite previous value")
|
||||||
}
|
}
|
||||||
|
|
@ -576,7 +576,7 @@ func BenchmarkOpMstore(bench *testing.B) {
|
||||||
for i := 0; i < bench.N; i++ {
|
for i := 0; i < bench.N; i++ {
|
||||||
stack.push(value)
|
stack.push(value)
|
||||||
stack.push(memStart)
|
stack.push(memStart)
|
||||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -591,7 +591,7 @@ func TestOpTstore(t *testing.T) {
|
||||||
to = common.Address{1}
|
to = common.Address{1}
|
||||||
contractRef = contractRef{caller}
|
contractRef = contractRef{caller}
|
||||||
contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0)
|
contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0)
|
||||||
scopeContext = ScopeContext{mem, stack, contract}
|
scopeContext = ScopeContext{mem, stack, contract, 0, nil}
|
||||||
value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700")
|
value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -639,7 +639,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
|
||||||
for i := 0; i < bench.N; i++ {
|
for i := 0; i < bench.N; i++ {
|
||||||
stack.push(uint256.NewInt(32))
|
stack.push(uint256.NewInt(32))
|
||||||
stack.push(start)
|
stack.push(start)
|
||||||
opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -734,7 +734,7 @@ func TestRandom(t *testing.T) {
|
||||||
pc = uint64(0)
|
pc = uint64(0)
|
||||||
evmInterpreter = env.interpreter
|
evmInterpreter = env.interpreter
|
||||||
)
|
)
|
||||||
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
|
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
||||||
if len(stack.data) != 1 {
|
if len(stack.data) != 1 {
|
||||||
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
|
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
|
||||||
}
|
}
|
||||||
|
|
@ -776,7 +776,7 @@ func TestBlobHash(t *testing.T) {
|
||||||
evmInterpreter = env.interpreter
|
evmInterpreter = env.interpreter
|
||||||
)
|
)
|
||||||
stack.push(uint256.NewInt(tt.idx))
|
stack.push(uint256.NewInt(tt.idx))
|
||||||
opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
|
opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
||||||
if len(stack.data) != 1 {
|
if len(stack.data) != 1 {
|
||||||
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
|
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
|
||||||
}
|
}
|
||||||
|
|
@ -917,7 +917,7 @@ func TestOpMCopy(t *testing.T) {
|
||||||
mem.Resize(memorySize)
|
mem.Resize(memorySize)
|
||||||
}
|
}
|
||||||
// Do the copy
|
// Do the copy
|
||||||
opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil})
|
||||||
want := common.FromHex(strings.ReplaceAll(tc.want, " ", ""))
|
want := common.FromHex(strings.ReplaceAll(tc.want, " ", ""))
|
||||||
if have := mem.store; !bytes.Equal(want, have) {
|
if have := mem.store; !bytes.Equal(want, have) {
|
||||||
t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have)
|
t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Tracer *tracing.Hooks
|
Tracer *tracing.Hooks
|
||||||
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
|
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
|
||||||
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
|
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimage
|
||||||
ExtraEips []int // Additional EIPS that are to be enabled
|
ExtraEips []int // Additional EIPS that are to be enabled
|
||||||
EnableWitnessCollection bool // true if witness collection is enabled
|
EnableWitnessCollection bool // true if witness collection is enabled
|
||||||
}
|
}
|
||||||
|
|
@ -42,6 +42,15 @@ type ScopeContext struct {
|
||||||
Memory *Memory
|
Memory *Memory
|
||||||
Stack *Stack
|
Stack *Stack
|
||||||
Contract *Contract
|
Contract *Contract
|
||||||
|
|
||||||
|
CodeSection uint64
|
||||||
|
ReturnStack []*ReturnContext
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReturnContext struct {
|
||||||
|
Section uint64
|
||||||
|
Pc uint64
|
||||||
|
StackHeight int
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryData returns the underlying memory slice. Callers must not modify the contents
|
// MemoryData returns the underlying memory slice. Callers must not modify the contents
|
||||||
|
|
@ -87,6 +96,7 @@ func (ctx *ScopeContext) CallInput() []byte {
|
||||||
type EVMInterpreter struct {
|
type EVMInterpreter struct {
|
||||||
evm *EVM
|
evm *EVM
|
||||||
table *JumpTable
|
table *JumpTable
|
||||||
|
tableEOF *JumpTable
|
||||||
|
|
||||||
hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes
|
hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes
|
||||||
hasherBuf common.Hash // Keccak256 hasher result array shared across opcodes
|
hasherBuf common.Hash // Keccak256 hasher result array shared across opcodes
|
||||||
|
|
@ -142,7 +152,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
evm.Config.ExtraEips = extraEips
|
evm.Config.ExtraEips = extraEips
|
||||||
return &EVMInterpreter{evm: evm, table: table}
|
return &EVMInterpreter{evm: evm, table: table, tableEOF: &shanghaiEOFInstructionSet}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run loops and evaluates the contract's code with the given input data and returns
|
// Run loops and evaluates the contract's code with the given input data and returns
|
||||||
|
|
@ -173,6 +183,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
jt *JumpTable // current jump table
|
||||||
op OpCode // current opcode
|
op OpCode // current opcode
|
||||||
mem = NewMemory() // bound memory
|
mem = NewMemory() // bound memory
|
||||||
stack = newstack() // local stack
|
stack = newstack() // local stack
|
||||||
|
|
@ -180,6 +191,8 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||||
Memory: mem,
|
Memory: mem,
|
||||||
Stack: stack,
|
Stack: stack,
|
||||||
Contract: contract,
|
Contract: contract,
|
||||||
|
CodeSection: 0,
|
||||||
|
ReturnStack: []*ReturnContext{{Section: 0, Pc: 0, StackHeight: 0}},
|
||||||
}
|
}
|
||||||
// For optimisation reason we're using uint64 as the program counter.
|
// 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
|
// It's theoretically possible to go above 2^64. The YP defines the PC
|
||||||
|
|
@ -202,6 +215,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||||
}()
|
}()
|
||||||
contract.Input = input
|
contract.Input = input
|
||||||
|
|
||||||
|
if contract.IsEOF() {
|
||||||
|
jt = in.tableEOF
|
||||||
|
} else {
|
||||||
|
jt = in.table
|
||||||
|
}
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
defer func() { // this deferred method handles exit-with-error
|
defer func() { // this deferred method handles exit-with-error
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -234,8 +253,8 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||||
|
|
||||||
// Get the operation from the jump table and validate the stack to ensure there are
|
// Get the operation from the jump table and validate the stack to ensure there are
|
||||||
// enough stack items available to perform the operation.
|
// enough stack items available to perform the operation.
|
||||||
op = contract.GetOp(pc)
|
op = contract.GetOp(pc, callContext.CodeSection)
|
||||||
operation := in.table[op]
|
operation := jt[op]
|
||||||
cost = operation.constantGas // For tracing
|
cost = operation.constantGas // For tracing
|
||||||
// Validate stack
|
// Validate stack
|
||||||
if sLen := stack.len(); sLen < operation.minStack {
|
if sLen := stack.len(); sLen < operation.minStack {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,12 @@ type operation struct {
|
||||||
|
|
||||||
// memorySize returns the memory size required for the operation
|
// memorySize returns the memory size required for the operation
|
||||||
memorySize memorySizeFunc
|
memorySize memorySizeFunc
|
||||||
|
|
||||||
|
// undefined denotes if the instruction is not officially defined in the jump table
|
||||||
|
undefined bool
|
||||||
|
|
||||||
|
// terminal denotes if the instruction can be the final opcode in a code section
|
||||||
|
terminal bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -58,6 +64,7 @@ var (
|
||||||
shanghaiInstructionSet = newShanghaiInstructionSet()
|
shanghaiInstructionSet = newShanghaiInstructionSet()
|
||||||
cancunInstructionSet = newCancunInstructionSet()
|
cancunInstructionSet = newCancunInstructionSet()
|
||||||
verkleInstructionSet = newVerkleInstructionSet()
|
verkleInstructionSet = newVerkleInstructionSet()
|
||||||
|
shanghaiEOFInstructionSet = newShanghaiEOFInstructionSet()
|
||||||
)
|
)
|
||||||
|
|
||||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||||
|
|
@ -98,6 +105,10 @@ func newCancunInstructionSet() JumpTable {
|
||||||
return validate(instructionSet)
|
return validate(instructionSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewShanghaiEOFInstructionSetForTesting() JumpTable {
|
||||||
|
return newShanghaiEOFInstructionSet()
|
||||||
|
}
|
||||||
|
|
||||||
func newShanghaiInstructionSet() JumpTable {
|
func newShanghaiInstructionSet() JumpTable {
|
||||||
instructionSet := newMergeInstructionSet()
|
instructionSet := newMergeInstructionSet()
|
||||||
enable3855(&instructionSet) // PUSH0 instruction
|
enable3855(&instructionSet) // PUSH0 instruction
|
||||||
|
|
@ -106,6 +117,12 @@ func newShanghaiInstructionSet() JumpTable {
|
||||||
return validate(instructionSet)
|
return validate(instructionSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newShanghaiEOFInstructionSet() JumpTable {
|
||||||
|
instructionSet := newShanghaiInstructionSet()
|
||||||
|
enableEOF(&instructionSet)
|
||||||
|
return validate(instructionSet)
|
||||||
|
}
|
||||||
|
|
||||||
func newMergeInstructionSet() JumpTable {
|
func newMergeInstructionSet() JumpTable {
|
||||||
instructionSet := newLondonInstructionSet()
|
instructionSet := newLondonInstructionSet()
|
||||||
instructionSet[PREVRANDAO] = &operation{
|
instructionSet[PREVRANDAO] = &operation{
|
||||||
|
|
@ -217,6 +234,7 @@ func newByzantiumInstructionSet() JumpTable {
|
||||||
minStack: minStack(2, 0),
|
minStack: minStack(2, 0),
|
||||||
maxStack: maxStack(2, 0),
|
maxStack: maxStack(2, 0),
|
||||||
memorySize: memoryRevert,
|
memorySize: memoryRevert,
|
||||||
|
terminal: true,
|
||||||
}
|
}
|
||||||
return validate(instructionSet)
|
return validate(instructionSet)
|
||||||
}
|
}
|
||||||
|
|
@ -265,6 +283,7 @@ func newFrontierInstructionSet() JumpTable {
|
||||||
constantGas: 0,
|
constantGas: 0,
|
||||||
minStack: minStack(0, 0),
|
minStack: minStack(0, 0),
|
||||||
maxStack: maxStack(0, 0),
|
maxStack: maxStack(0, 0),
|
||||||
|
terminal: true,
|
||||||
},
|
},
|
||||||
ADD: {
|
ADD: {
|
||||||
execute: opAdd,
|
execute: opAdd,
|
||||||
|
|
@ -1053,6 +1072,7 @@ func newFrontierInstructionSet() JumpTable {
|
||||||
minStack: minStack(2, 0),
|
minStack: minStack(2, 0),
|
||||||
maxStack: maxStack(2, 0),
|
maxStack: maxStack(2, 0),
|
||||||
memorySize: memoryReturn,
|
memorySize: memoryReturn,
|
||||||
|
terminal: true,
|
||||||
},
|
},
|
||||||
SELFDESTRUCT: {
|
SELFDESTRUCT: {
|
||||||
execute: opSelfdestruct,
|
execute: opSelfdestruct,
|
||||||
|
|
@ -1060,12 +1080,18 @@ func newFrontierInstructionSet() JumpTable {
|
||||||
minStack: minStack(1, 0),
|
minStack: minStack(1, 0),
|
||||||
maxStack: maxStack(1, 0),
|
maxStack: maxStack(1, 0),
|
||||||
},
|
},
|
||||||
|
INVALID: {
|
||||||
|
execute: opUndefined,
|
||||||
|
minStack: minStack(0, 0),
|
||||||
|
maxStack: maxStack(0, 0),
|
||||||
|
terminal: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill all unassigned slots with opUndefined.
|
// Fill all unassigned slots with opUndefined.
|
||||||
for i, entry := range tbl {
|
for i, entry := range tbl {
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0)}
|
tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0), undefined: true}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,9 @@ const (
|
||||||
TLOAD OpCode = 0x5c
|
TLOAD OpCode = 0x5c
|
||||||
TSTORE OpCode = 0x5d
|
TSTORE OpCode = 0x5d
|
||||||
MCOPY OpCode = 0x5e
|
MCOPY OpCode = 0x5e
|
||||||
|
RJUMP OpCode = 0x5c
|
||||||
|
RJUMPI OpCode = 0x5d
|
||||||
|
RJUMPV OpCode = 0x5e
|
||||||
PUSH0 OpCode = 0x5f
|
PUSH0 OpCode = 0x5f
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -209,6 +212,12 @@ const (
|
||||||
LOG4
|
LOG4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 0xb0 range - control flow ops.
|
||||||
|
const (
|
||||||
|
CALLF = 0xb0
|
||||||
|
RETF = 0xb1
|
||||||
|
)
|
||||||
|
|
||||||
// 0xf0 range - closures.
|
// 0xf0 range - closures.
|
||||||
const (
|
const (
|
||||||
CREATE OpCode = 0xf0
|
CREATE OpCode = 0xf0
|
||||||
|
|
@ -305,6 +314,10 @@ var opCodeToString = [256]string{
|
||||||
TLOAD: "TLOAD",
|
TLOAD: "TLOAD",
|
||||||
TSTORE: "TSTORE",
|
TSTORE: "TSTORE",
|
||||||
MCOPY: "MCOPY",
|
MCOPY: "MCOPY",
|
||||||
|
// TODO (MariusVanDerWijden) reenable once moved
|
||||||
|
//RJUMP: "RJUMP",
|
||||||
|
//RJUMPI: "RJUMPI",
|
||||||
|
//RJUMPV: "RJUMPV",
|
||||||
PUSH0: "PUSH0",
|
PUSH0: "PUSH0",
|
||||||
|
|
||||||
// 0x60 range - pushes.
|
// 0x60 range - pushes.
|
||||||
|
|
@ -384,6 +397,10 @@ var opCodeToString = [256]string{
|
||||||
LOG3: "LOG3",
|
LOG3: "LOG3",
|
||||||
LOG4: "LOG4",
|
LOG4: "LOG4",
|
||||||
|
|
||||||
|
// 0xb0 range.
|
||||||
|
CALLF: "CALLF",
|
||||||
|
RETF: "RETF",
|
||||||
|
|
||||||
// 0xf0 range - closures.
|
// 0xf0 range - closures.
|
||||||
CREATE: "CREATE",
|
CREATE: "CREATE",
|
||||||
CALL: "CALL",
|
CALL: "CALL",
|
||||||
|
|
|
||||||
229
core/vm/validate.go
Normal file
229
core/vm/validate.go
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
//
|
||||||
|
// 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 (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUndefinedInstruction = errors.New("undefined instrustion")
|
||||||
|
ErrTruncatedImmediate = errors.New("truncated immediate")
|
||||||
|
ErrInvalidSectionArgument = errors.New("invalid section argument")
|
||||||
|
ErrInvalidJumpDest = errors.New("invalid jump destination")
|
||||||
|
ErrConflictingStack = errors.New("conflicting stack height")
|
||||||
|
ErrInvalidBranchCount = errors.New("invalid number of branches in jump table")
|
||||||
|
ErrInvalidOutputs = errors.New("invalid number of outputs")
|
||||||
|
ErrInvalidMaxStackHeight = errors.New("invalid max stack height")
|
||||||
|
ErrInvalidCodeTermination = errors.New("invalid code termination")
|
||||||
|
ErrUnreachableCode = errors.New("unreachable code")
|
||||||
|
)
|
||||||
|
|
||||||
|
// validateCode validates the code parameter against the EOF v1 validity requirements.
|
||||||
|
func validateCode(code []byte, section int, metadata []*FunctionMetadata, jt *JumpTable) error {
|
||||||
|
var (
|
||||||
|
i = 0
|
||||||
|
// Tracks the number of actual instructions in the code (e.g.
|
||||||
|
// non-immediate values). This is used at the end to determine
|
||||||
|
// if each instruction is reachable.
|
||||||
|
count = 0
|
||||||
|
op OpCode
|
||||||
|
analysis bitvec
|
||||||
|
)
|
||||||
|
// This loop visits every single instruction and verifies:
|
||||||
|
// * if the instruction is valid for the given jump table.
|
||||||
|
// * if the instruction has an immediate value, it is not truncated.
|
||||||
|
// * if performing a relative jump, all jump destinations are valid.
|
||||||
|
// * if changing code sections, the new code section index is valid and
|
||||||
|
// will not cause a stack overflow.
|
||||||
|
for i < len(code) {
|
||||||
|
count++
|
||||||
|
op = OpCode(code[i])
|
||||||
|
if jt[op].undefined {
|
||||||
|
return fmt.Errorf("%w: op %s, pos %d", ErrUndefinedInstruction, op, i)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case op >= PUSH1 && op <= PUSH32:
|
||||||
|
size := int(op - PUSH0)
|
||||||
|
if len(code) <= i+size {
|
||||||
|
return fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
|
||||||
|
}
|
||||||
|
i += size
|
||||||
|
case op == RJUMP || op == RJUMPI:
|
||||||
|
if len(code) <= i+2 {
|
||||||
|
return fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
|
||||||
|
}
|
||||||
|
if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i += 2
|
||||||
|
case op == RJUMPV:
|
||||||
|
if len(code) <= i+1 {
|
||||||
|
return fmt.Errorf("%w: jump table size missing, op %s, pos %d", ErrTruncatedImmediate, op, i)
|
||||||
|
}
|
||||||
|
count := int(code[i+1])
|
||||||
|
if count == 0 {
|
||||||
|
return fmt.Errorf("%w: must not be 0, pos %d", ErrInvalidBranchCount, i)
|
||||||
|
}
|
||||||
|
if len(code) <= i+count {
|
||||||
|
return fmt.Errorf("%w: jump table truncated, op %s, pos %d", ErrTruncatedImmediate, op, i)
|
||||||
|
}
|
||||||
|
for j := 0; j < count; j++ {
|
||||||
|
if err := checkDest(code, &analysis, i+2+j*2, i+2*count+2, len(code)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1 + 2*count
|
||||||
|
case op == CALLF:
|
||||||
|
if i+2 >= len(code) {
|
||||||
|
return fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
|
||||||
|
}
|
||||||
|
arg, _ := parseUint16(code[i+1:])
|
||||||
|
if arg >= len(metadata) {
|
||||||
|
return fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidSectionArgument, arg, len(metadata), i)
|
||||||
|
}
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
// Code sections may not "fall through" and require proper termination.
|
||||||
|
// Therefore, the last instruction must be considered terminal.
|
||||||
|
if !jt[op].terminal {
|
||||||
|
return fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, i)
|
||||||
|
}
|
||||||
|
if paths, err := validateControlFlow(code, section, metadata, jt); err != nil {
|
||||||
|
return err
|
||||||
|
} else if paths != count {
|
||||||
|
// TODO(matt): return actual position of unreacable code
|
||||||
|
return ErrUnreachableCode
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkDest parses a relative offset at code[0:2] and checks if it is a valid jump destination.
|
||||||
|
func checkDest(code []byte, analysis *bitvec, imm, from, length int) error {
|
||||||
|
if len(code) < imm+2 {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if analysis != nil && *analysis == nil {
|
||||||
|
*analysis = eofCodeBitmap(code)
|
||||||
|
}
|
||||||
|
offset := parseInt16(code[imm:])
|
||||||
|
dest := from + offset
|
||||||
|
if dest < 0 || dest >= length {
|
||||||
|
return fmt.Errorf("%w: out-of-bounds offset: offset %d, dest %d, pos %d", ErrInvalidJumpDest, offset, dest, imm)
|
||||||
|
}
|
||||||
|
if !analysis.codeSegment(uint64(dest)) {
|
||||||
|
return fmt.Errorf("%w: offset into immediate: offset %d, dest %d, pos %d", ErrInvalidJumpDest, offset, dest, imm)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateControlFlow iterates through all possible branches the provided code
|
||||||
|
// value and determines if it is valid per EOF v1.
|
||||||
|
func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata, jt *JumpTable) (int, error) {
|
||||||
|
type item struct {
|
||||||
|
pos int
|
||||||
|
height int
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
heights = make(map[int]int)
|
||||||
|
worklist = []item{{0, int(metadata[section].Input)}}
|
||||||
|
maxStackHeight = int(metadata[section].Input)
|
||||||
|
)
|
||||||
|
for 0 < len(worklist) {
|
||||||
|
var (
|
||||||
|
idx = len(worklist) - 1
|
||||||
|
pos = worklist[idx].pos
|
||||||
|
height = worklist[idx].height
|
||||||
|
)
|
||||||
|
worklist = worklist[:idx]
|
||||||
|
outer:
|
||||||
|
for pos < len(code) {
|
||||||
|
op := OpCode(code[pos])
|
||||||
|
|
||||||
|
// Check if pos has already be visited; if so, the stack heights should be the same.
|
||||||
|
if want, ok := heights[pos]; ok {
|
||||||
|
if height != want {
|
||||||
|
return 0, fmt.Errorf("%w: have %d, want %d", ErrConflictingStack, height, want)
|
||||||
|
}
|
||||||
|
// Already visited this path and stack height
|
||||||
|
// matches.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
heights[pos] = height
|
||||||
|
|
||||||
|
// Validate height for current op and update as needed.
|
||||||
|
if want, have := jt[op].minStack, height; want > have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
|
}
|
||||||
|
if want, have := jt[op].maxStack, height; want < have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: want}, pos)
|
||||||
|
}
|
||||||
|
height += int(params.StackLimit) - jt[op].maxStack
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case op == CALLF:
|
||||||
|
arg, _ := parseUint16(code[pos+1:])
|
||||||
|
if want, have := int(metadata[arg].Input), height; want > have {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
|
}
|
||||||
|
if have, limit := int(metadata[arg].Output)+height, int(params.StackLimit); have > limit {
|
||||||
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
||||||
|
}
|
||||||
|
height -= int(metadata[arg].Input)
|
||||||
|
height += int(metadata[arg].Output)
|
||||||
|
pos += 3
|
||||||
|
case op == RETF:
|
||||||
|
if have, want := int(metadata[section].Output), height; have != want {
|
||||||
|
return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", ErrInvalidOutputs, have, want, pos)
|
||||||
|
}
|
||||||
|
break outer
|
||||||
|
case op == RJUMP:
|
||||||
|
arg := parseInt16(code[pos+1:])
|
||||||
|
pos += 3 + arg
|
||||||
|
case op == RJUMPI:
|
||||||
|
arg := parseInt16(code[pos+1:])
|
||||||
|
worklist = append(worklist, item{pos: pos + 3 + arg, height: height})
|
||||||
|
pos += 3
|
||||||
|
case op == RJUMPV:
|
||||||
|
count := int(code[pos+1])
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
arg := parseInt16(code[pos+2+2*i:])
|
||||||
|
worklist = append(worklist, item{pos: pos + 2 + 2*count + arg, height: height})
|
||||||
|
}
|
||||||
|
pos += 2 + 2*count
|
||||||
|
default:
|
||||||
|
if op >= PUSH1 && op <= PUSH32 {
|
||||||
|
pos += 1 + int(op-PUSH0)
|
||||||
|
} else if jt[op].terminal {
|
||||||
|
break outer
|
||||||
|
} else {
|
||||||
|
// Simple op, no operand.
|
||||||
|
pos += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maxStackHeight = max(maxStackHeight, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxStackHeight != int(metadata[section].MaxStackHeight) {
|
||||||
|
return 0, fmt.Errorf("%w in code section %d: have %d, want %d", ErrInvalidMaxStackHeight, section, maxStackHeight, metadata[section].MaxStackHeight)
|
||||||
|
}
|
||||||
|
return len(heights), nil
|
||||||
|
}
|
||||||
250
core/vm/validate_test.go
Normal file
250
core/vm/validate_test.go
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
// Copyright 2022 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 (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateCode(t *testing.T) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
code []byte
|
||||||
|
section int
|
||||||
|
metadata []*FunctionMetadata
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(CALLER),
|
||||||
|
byte(POP),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(CALLF), 0x00, 0x00,
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 0}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(ADDRESS),
|
||||||
|
byte(CALLF), 0x00, 0x00,
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(CALLER),
|
||||||
|
byte(POP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
|
err: ErrInvalidCodeTermination,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(RJUMP),
|
||||||
|
byte(0x00),
|
||||||
|
byte(0x01),
|
||||||
|
byte(CALLER),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 0}},
|
||||||
|
err: ErrUnreachableCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH1),
|
||||||
|
byte(0x42),
|
||||||
|
byte(ADD),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
|
err: ErrStackUnderflow{stackLen: 1, required: 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH1),
|
||||||
|
byte(0x42),
|
||||||
|
byte(POP),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 2}},
|
||||||
|
err: ErrInvalidMaxStackHeight,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH0),
|
||||||
|
byte(RJUMPI),
|
||||||
|
byte(0x00),
|
||||||
|
byte(0x01),
|
||||||
|
byte(PUSH1),
|
||||||
|
byte(0x42), // jumps to here
|
||||||
|
byte(POP),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
|
err: ErrInvalidJumpDest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH0),
|
||||||
|
byte(RJUMPV),
|
||||||
|
byte(0x02),
|
||||||
|
byte(0x00),
|
||||||
|
byte(0x01),
|
||||||
|
byte(0x00),
|
||||||
|
byte(0x02),
|
||||||
|
byte(PUSH1),
|
||||||
|
byte(0x42), // jumps to here
|
||||||
|
byte(POP), // and here
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
|
err: ErrInvalidJumpDest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH0),
|
||||||
|
byte(RJUMPV),
|
||||||
|
byte(0x00),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}},
|
||||||
|
err: ErrInvalidBranchCount,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(RJUMP), 0x00, 0x03,
|
||||||
|
byte(JUMPDEST),
|
||||||
|
byte(JUMPDEST),
|
||||||
|
byte(RETURN),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 39,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(CODECOPY),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(RJUMP), 0xff, 0xef,
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH1), 1,
|
||||||
|
byte(RJUMPI), 0x00, 0x03,
|
||||||
|
byte(JUMPDEST),
|
||||||
|
byte(JUMPDEST),
|
||||||
|
byte(STOP),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 39,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(CODECOPY),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(RETURN),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(PUSH1), 1,
|
||||||
|
byte(RJUMPV), 0x02, 0x00, 0x03, 0xff, 0xf8,
|
||||||
|
byte(JUMPDEST),
|
||||||
|
byte(JUMPDEST),
|
||||||
|
byte(STOP),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 39,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(CODECOPY),
|
||||||
|
byte(PUSH1), 20,
|
||||||
|
byte(PUSH1), 0x00,
|
||||||
|
byte(RETURN),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(STOP),
|
||||||
|
byte(STOP),
|
||||||
|
byte(INVALID),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 0}},
|
||||||
|
err: ErrUnreachableCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(RETF),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 1, MaxStackHeight: 0}},
|
||||||
|
err: ErrInvalidOutputs,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(RETF),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 3, Output: 3, MaxStackHeight: 3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(CALLF), 0x00, 0x01,
|
||||||
|
byte(POP),
|
||||||
|
byte(STOP),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}, {Input: 0, Output: 1, MaxStackHeight: 0}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: []byte{
|
||||||
|
byte(ORIGIN),
|
||||||
|
byte(ORIGIN),
|
||||||
|
byte(CALLF), 0x00, 0x01,
|
||||||
|
byte(POP),
|
||||||
|
byte(RETF),
|
||||||
|
},
|
||||||
|
section: 0,
|
||||||
|
metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 2}, {Input: 2, Output: 1, MaxStackHeight: 2}},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
err := validateCode(test.code, test.section, test.metadata, &shanghaiEOFInstructionSet)
|
||||||
|
if !errors.Is(err, test.err) {
|
||||||
|
t.Errorf("test %d (%s): unexpected error (want: %v, got: %v)", i, common.Bytes2Hex(test.code), test.err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue