mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12: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 {
|
||||
return err
|
||||
}
|
||||
if err := applyEOFChecks(&prestate, chainConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
// Run the test and aggregate the result
|
||||
s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name), getTracer)
|
||||
if err != nil {
|
||||
|
|
@ -285,6 +288,29 @@ func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error {
|
|||
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
|
||||
|
||||
func (g Alloc) OnRoot(common.Hash) {}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,13 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"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/flags"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
// Force-load the tracer engines to trigger registration
|
||||
|
|
@ -138,6 +141,26 @@ var (
|
|||
Usage: "enable return data output",
|
||||
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{
|
||||
|
|
@ -222,6 +245,17 @@ var traceFlags = []cli.Flag{
|
|||
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")
|
||||
|
||||
func init() {
|
||||
|
|
@ -235,6 +269,7 @@ func init() {
|
|||
stateTransitionCommand,
|
||||
transactionCommand,
|
||||
blockBuilderCommand,
|
||||
eofParserCommand,
|
||||
}
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
flags.MigrateGlobalFlags(ctx)
|
||||
|
|
|
|||
|
|
@ -284,8 +284,6 @@ func TestT8n(t *testing.T) {
|
|||
input: t8nInput{
|
||||
"alloc.json", "txs_more.rlp", "env.json", "Cancun", "",
|
||||
},
|
||||
output: t8nOutput{alloc: true, result: true},
|
||||
expOut: "exp.json",
|
||||
},
|
||||
} {
|
||||
args := []string{"t8n"}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package core
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
|
@ -4422,9 +4423,6 @@ func TestRequests(t *testing.T) {
|
|||
t.Fatalf("failed to create tester chain: %v", err)
|
||||
}
|
||||
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.
|
||||
block := chain.GetBlockByNumber(1)
|
||||
|
|
@ -4476,6 +4474,352 @@ func TestRequests(t *testing.T) {
|
|||
}
|
||||
if want.TargetPublicKey != 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 (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
|
@ -512,6 +513,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
|||
)
|
||||
if contractCreation {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
// 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"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
|
|
@ -56,6 +57,28 @@ func TestJumpDestAnalysis(t *testing.T) {
|
|||
if ret[test.which] != test.exp {
|
||||
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
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,10 +52,11 @@ type Contract struct {
|
|||
jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis.
|
||||
analysis bitvec // Locally cached result of JUMPDEST analysis
|
||||
|
||||
Code []byte
|
||||
CodeHash common.Hash
|
||||
CodeAddr *common.Address
|
||||
Input []byte
|
||||
Code []byte
|
||||
Container *Container
|
||||
CodeHash common.Hash
|
||||
CodeAddr *common.Address
|
||||
Input []byte
|
||||
|
||||
// is the execution frame represented by this object a contract deployment
|
||||
IsDeployment bool
|
||||
|
|
@ -144,11 +145,12 @@ func (c *Contract) AsDelegate() *Contract {
|
|||
}
|
||||
|
||||
// GetOp returns the n'th element in the contract's byte array
|
||||
func (c *Contract) GetOp(n uint64) OpCode {
|
||||
if n < uint64(len(c.Code)) {
|
||||
func (c *Contract) GetOp(n uint64, s uint64) OpCode {
|
||||
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 STOP
|
||||
}
|
||||
|
||||
|
|
@ -193,10 +195,23 @@ func (c *Contract) Value() *uint256.Int {
|
|||
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
|
||||
// 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.Container = container
|
||||
c.CodeHash = hash
|
||||
c.CodeAddr = addr
|
||||
}
|
||||
|
|
|
|||
134
core/vm/eips.go
134
core/vm/eips.go
|
|
@ -21,6 +21,8 @@ import (
|
|||
"math"
|
||||
"sort"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -557,3 +559,135 @@ func enable7702(jt *JumpTable) {
|
|||
jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929
|
||||
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")
|
||||
ErrReturnDataOutOfBounds = errors.New("return data out of bounds")
|
||||
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")
|
||||
ErrInvalidEOF = errors.New("invalid eof")
|
||||
ErrInvalidEOFInitcode = errors.New("invalid eof initcode")
|
||||
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
|
||||
|
||||
// errStopToken is an internal token indicating interpreter loop termination,
|
||||
|
|
@ -51,10 +54,14 @@ type ErrStackUnderflow struct {
|
|||
required int
|
||||
}
|
||||
|
||||
func (e *ErrStackUnderflow) Error() string {
|
||||
func (e ErrStackUnderflow) Error() string {
|
||||
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
|
||||
// the maximum allowance.
|
||||
type ErrStackOverflow struct {
|
||||
|
|
@ -62,10 +69,14 @@ type ErrStackOverflow struct {
|
|||
limit int
|
||||
}
|
||||
|
||||
func (e *ErrStackOverflow) Error() string {
|
||||
func (e ErrStackOverflow) Error() string {
|
||||
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.
|
||||
type ErrInvalidOpCode struct {
|
||||
opcode OpCode
|
||||
|
|
|
|||
178
core/vm/evm.go
178
core/vm/evm.go
|
|
@ -18,7 +18,9 @@ package vm
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"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
|
||||
// The depth-check is already done, and precompiles handled above
|
||||
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)
|
||||
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)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
code := evm.StateDB.GetCode(addrCopy)
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, 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.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)
|
||||
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)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
code := evm.StateDB.GetCode(addrCopy)
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
||||
if witness := evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCode(evm.StateDB.GetCode(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)
|
||||
gas = contract.Gas
|
||||
}
|
||||
|
|
@ -411,12 +415,13 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
|||
addrCopy := addr
|
||||
// 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.
|
||||
code := evm.StateDB.GetCode(addrCopy)
|
||||
contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas)
|
||||
if witness := evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCode(evm.StateDB.GetCode(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
|
||||
// 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.
|
||||
|
|
@ -449,7 +454,7 @@ func (c *codeAndHash) Hash() common.Hash {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
evm.captureBegin(evm.depth, typ, caller.Address(), address, codeAndHash.code, gas, value.ToBig())
|
||||
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) {
|
||||
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())
|
||||
if nonce+1 < nonce {
|
||||
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)
|
||||
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, AccountRef(address), value, gas)
|
||||
contract.SetCodeOptionalHash(&address, codeAndHash)
|
||||
contract.IsDeployment = true
|
||||
// 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) {
|
||||
err = ErrOutOfGas
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
|
|
@ -524,55 +614,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||
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.
|
||||
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()))
|
||||
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.
|
||||
|
|
@ -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) {
|
||||
codeAndHash := &codeAndHash{code: code}
|
||||
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
|
||||
|
|
@ -628,3 +675,18 @@ func (evm *EVM) GetVMContext() *tracing.VMContext {
|
|||
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 (
|
||||
GasQuickStep uint64 = 2
|
||||
GasFastestStep uint64 = 3
|
||||
GasFastishStep uint64 = 4
|
||||
GasFastStep uint64 = 5
|
||||
GasMidStep uint64 = 8
|
||||
GasSlowStep uint64 = 10
|
||||
|
|
|
|||
|
|
@ -969,12 +969,13 @@ func makeLog(size int) executionFunc {
|
|||
// opPush1 is a specialized version of pushN
|
||||
func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
var (
|
||||
codeLen = uint64(len(scope.Contract.Code))
|
||||
code = scope.Contract.CodeAt(scope.CodeSection)
|
||||
codeLen = uint64(len(code))
|
||||
integer = new(uint256.Int)
|
||||
)
|
||||
*pc += 1
|
||||
if *pc < codeLen {
|
||||
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
|
||||
scope.Stack.push(integer.SetUint64(uint64(code[*pc])))
|
||||
} else {
|
||||
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))
|
||||
stack.push(x)
|
||||
stack.push(y)
|
||||
opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
|
||||
opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
||||
if len(stack.data) != 1 {
|
||||
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(y)
|
||||
stack.push(x)
|
||||
opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
|
||||
opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
||||
actual := stack.pop()
|
||||
if actual.Cmp(expected) != 0 {
|
||||
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))
|
||||
stack.push(x)
|
||||
stack.push(y)
|
||||
opFn(&pc, interpreter, &ScopeContext{nil, stack, nil})
|
||||
opFn(&pc, interpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
||||
actual := stack.pop()
|
||||
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 (
|
||||
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
|
||||
stack = newstack()
|
||||
scope = &ScopeContext{nil, stack, nil}
|
||||
scope = &ScopeContext{nil, stack, nil, 0, nil}
|
||||
evmInterpreter = NewEVMInterpreter(env)
|
||||
)
|
||||
|
||||
|
|
@ -546,13 +546,13 @@ func TestOpMstore(t *testing.T) {
|
|||
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
|
||||
stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v)))
|
||||
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 {
|
||||
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
|
||||
}
|
||||
stack.push(new(uint256.Int).SetUint64(0x1))
|
||||
stack.push(new(uint256.Int))
|
||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil})
|
||||
if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
|
||||
t.Fatalf("Mstore failed to overwrite previous value")
|
||||
}
|
||||
|
|
@ -576,7 +576,7 @@ func BenchmarkOpMstore(bench *testing.B) {
|
|||
for i := 0; i < bench.N; i++ {
|
||||
stack.push(value)
|
||||
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}
|
||||
contractRef = contractRef{caller}
|
||||
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")
|
||||
)
|
||||
|
||||
|
|
@ -639,7 +639,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
|
|||
for i := 0; i < bench.N; i++ {
|
||||
stack.push(uint256.NewInt(32))
|
||||
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)
|
||||
evmInterpreter = env.interpreter
|
||||
)
|
||||
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
|
||||
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil})
|
||||
if len(stack.data) != 1 {
|
||||
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
|
||||
)
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
// 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, " ", ""))
|
||||
if have := mem.store; !bytes.Equal(want, have) {
|
||||
t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import (
|
|||
type Config struct {
|
||||
Tracer *tracing.Hooks
|
||||
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
|
||||
EnableWitnessCollection bool // true if witness collection is enabled
|
||||
}
|
||||
|
|
@ -42,6 +42,15 @@ type ScopeContext struct {
|
|||
Memory *Memory
|
||||
Stack *Stack
|
||||
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
|
||||
|
|
@ -85,8 +94,9 @@ func (ctx *ScopeContext) CallInput() []byte {
|
|||
|
||||
// EVMInterpreter represents an EVM interpreter
|
||||
type EVMInterpreter struct {
|
||||
evm *EVM
|
||||
table *JumpTable
|
||||
evm *EVM
|
||||
table *JumpTable
|
||||
tableEOF *JumpTable
|
||||
|
||||
hasher crypto.KeccakState // Keccak256 hasher instance 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
|
||||
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
|
||||
|
|
@ -173,13 +183,16 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
}
|
||||
|
||||
var (
|
||||
jt *JumpTable // current jump table
|
||||
op OpCode // current opcode
|
||||
mem = NewMemory() // bound memory
|
||||
stack = newstack() // local stack
|
||||
callContext = &ScopeContext{
|
||||
Memory: mem,
|
||||
Stack: stack,
|
||||
Contract: contract,
|
||||
Memory: mem,
|
||||
Stack: stack,
|
||||
Contract: contract,
|
||||
CodeSection: 0,
|
||||
ReturnStack: []*ReturnContext{{Section: 0, Pc: 0, StackHeight: 0}},
|
||||
}
|
||||
// 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
|
||||
|
|
@ -202,6 +215,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
}()
|
||||
contract.Input = input
|
||||
|
||||
if contract.IsEOF() {
|
||||
jt = in.tableEOF
|
||||
} else {
|
||||
jt = in.table
|
||||
}
|
||||
|
||||
if debug {
|
||||
defer func() { // this deferred method handles exit-with-error
|
||||
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
|
||||
// enough stack items available to perform the operation.
|
||||
op = contract.GetOp(pc)
|
||||
operation := in.table[op]
|
||||
op = contract.GetOp(pc, callContext.CodeSection)
|
||||
operation := jt[op]
|
||||
cost = operation.constantGas // For tracing
|
||||
// Validate stack
|
||||
if sLen := stack.len(); sLen < operation.minStack {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@ type operation struct {
|
|||
|
||||
// memorySize returns the memory size required for the operation
|
||||
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 (
|
||||
|
|
@ -58,6 +64,7 @@ var (
|
|||
shanghaiInstructionSet = newShanghaiInstructionSet()
|
||||
cancunInstructionSet = newCancunInstructionSet()
|
||||
verkleInstructionSet = newVerkleInstructionSet()
|
||||
shanghaiEOFInstructionSet = newShanghaiEOFInstructionSet()
|
||||
)
|
||||
|
||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||
|
|
@ -98,6 +105,10 @@ func newCancunInstructionSet() JumpTable {
|
|||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func NewShanghaiEOFInstructionSetForTesting() JumpTable {
|
||||
return newShanghaiEOFInstructionSet()
|
||||
}
|
||||
|
||||
func newShanghaiInstructionSet() JumpTable {
|
||||
instructionSet := newMergeInstructionSet()
|
||||
enable3855(&instructionSet) // PUSH0 instruction
|
||||
|
|
@ -106,6 +117,12 @@ func newShanghaiInstructionSet() JumpTable {
|
|||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func newShanghaiEOFInstructionSet() JumpTable {
|
||||
instructionSet := newShanghaiInstructionSet()
|
||||
enableEOF(&instructionSet)
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func newMergeInstructionSet() JumpTable {
|
||||
instructionSet := newLondonInstructionSet()
|
||||
instructionSet[PREVRANDAO] = &operation{
|
||||
|
|
@ -217,6 +234,7 @@ func newByzantiumInstructionSet() JumpTable {
|
|||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
memorySize: memoryRevert,
|
||||
terminal: true,
|
||||
}
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
|
@ -265,6 +283,7 @@ func newFrontierInstructionSet() JumpTable {
|
|||
constantGas: 0,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
terminal: true,
|
||||
},
|
||||
ADD: {
|
||||
execute: opAdd,
|
||||
|
|
@ -1053,6 +1072,7 @@ func newFrontierInstructionSet() JumpTable {
|
|||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
memorySize: memoryReturn,
|
||||
terminal: true,
|
||||
},
|
||||
SELFDESTRUCT: {
|
||||
execute: opSelfdestruct,
|
||||
|
|
@ -1060,12 +1080,18 @@ func newFrontierInstructionSet() JumpTable {
|
|||
minStack: minStack(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.
|
||||
for i, entry := range tbl {
|
||||
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
|
||||
TSTORE OpCode = 0x5d
|
||||
MCOPY OpCode = 0x5e
|
||||
RJUMP OpCode = 0x5c
|
||||
RJUMPI OpCode = 0x5d
|
||||
RJUMPV OpCode = 0x5e
|
||||
PUSH0 OpCode = 0x5f
|
||||
)
|
||||
|
||||
|
|
@ -209,6 +212,12 @@ const (
|
|||
LOG4
|
||||
)
|
||||
|
||||
// 0xb0 range - control flow ops.
|
||||
const (
|
||||
CALLF = 0xb0
|
||||
RETF = 0xb1
|
||||
)
|
||||
|
||||
// 0xf0 range - closures.
|
||||
const (
|
||||
CREATE OpCode = 0xf0
|
||||
|
|
@ -305,7 +314,11 @@ var opCodeToString = [256]string{
|
|||
TLOAD: "TLOAD",
|
||||
TSTORE: "TSTORE",
|
||||
MCOPY: "MCOPY",
|
||||
PUSH0: "PUSH0",
|
||||
// TODO (MariusVanDerWijden) reenable once moved
|
||||
//RJUMP: "RJUMP",
|
||||
//RJUMPI: "RJUMPI",
|
||||
//RJUMPV: "RJUMPV",
|
||||
PUSH0: "PUSH0",
|
||||
|
||||
// 0x60 range - pushes.
|
||||
PUSH1: "PUSH1",
|
||||
|
|
@ -384,6 +397,10 @@ var opCodeToString = [256]string{
|
|||
LOG3: "LOG3",
|
||||
LOG4: "LOG4",
|
||||
|
||||
// 0xb0 range.
|
||||
CALLF: "CALLF",
|
||||
RETF: "RETF",
|
||||
|
||||
// 0xf0 range - closures.
|
||||
CREATE: "CREATE",
|
||||
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