diff --git a/cmd/evm/eofparser.go b/cmd/eofdump/eofparser.go similarity index 64% rename from cmd/evm/eofparser.go rename to cmd/eofdump/eofparser.go index 7cc27c0b34..a494a74ede 100644 --- a/cmd/evm/eofparser.go +++ b/cmd/eofdump/eofparser.go @@ -23,8 +23,11 @@ import ( "errors" "fmt" "io" + "io/fs" "os" + "path/filepath" "strings" + "sync/atomic" "github.com/ethereum/go-ethereum/core/vm" "github.com/urfave/cli/v2" @@ -64,14 +67,18 @@ var ( } ) +type RefTests struct { + Vectors map[string]EOFTest `json:"vectors"` +} + 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"` + Result bool `json:"result"` + Exception string `json:"exception,omitempty"` } func eofParser(ctx *cli.Context) error { @@ -89,41 +96,44 @@ func eofParser(ctx *cli.Context) error { // 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 { + var ( + file = ctx.String(RefTestFlag.Name) + executedTests atomic.Int32 + passedTests atomic.Int32 + ) + if info, err := os.Stat(file); 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++ + } else if !info.IsDir() { + src, err := os.ReadFile(file) + if err != nil { + return err } + _, _, err = ExecuteTest(src) + return err + } else { + err = filepath.Walk(file, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + fmt.Printf("Executing Tests: %v\n", info.Name()) + src, err := os.ReadFile(path) + if err != nil { + return err + } + passed, total, err := ExecuteTest(src) + passedTests.Add(int32(passed)) + executedTests.Add(int32(total)) + return err + }) + if err != nil { + return err + } + fmt.Printf("Passed %v tests out of %v\n", passedTests.Load(), executedTests.Load()) + return nil } - fmt.Printf("%d/%d tests passed.\n", passed, total) - return nil } // If neither are passed in, read input from stdin. @@ -144,6 +154,45 @@ func eofParser(ctx *cli.Context) error { return nil } +func ExecuteTest(src []byte) (int, int, error) { + var testsByName map[string]RefTests + if err := json.Unmarshal(src, &testsByName); err != nil { + return 0, 0, err + } + passed, total := 0, 0 + for _, tests := range testsByName { + for name, tt := range tests.Vectors { + 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 %s, got %v\n", name, fork, r.Exception, err) + continue + } + /* + // TODO (MariusVanDerWijden) reenable once tests have a decent error format + if !r.Result && err != nil && r.Exception != 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 passed, total, nil +} + func parseAndValidate(s string) (*vm.Container, error) { if len(s) >= 2 && strings.HasPrefix(s, "0x") { s = s[2:] @@ -161,3 +210,24 @@ func parseAndValidate(s string) (*vm.Container, error) { } return &c, nil } + +func eofDump(ctx *cli.Context) error { + // If `--hex` is set, parse and validate the hex string argument. + if ctx.IsSet(HexFlag.Name) { + s := ctx.String(HexFlag.Name) + if len(s) >= 2 && strings.HasPrefix(s, "0x") { + s = s[2:] + } + b, err := hex.DecodeString(s) + if err != nil { + return fmt.Errorf("unable to decode data: %w", err) + } + var c vm.Container + if err := c.UnmarshalBinary(b); err != nil { + return err + } + fmt.Print(c.String()) + return nil + } + return nil +} diff --git a/cmd/eofdump/main.go b/cmd/eofdump/main.go new file mode 100644 index 0000000000..d014410cf4 --- /dev/null +++ b/cmd/eofdump/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "os" + + "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/urfave/cli/v2" +) + +var app = flags.NewApp("the evm command line interface") + +var ( + RefTestFlag = &cli.StringFlag{ + Name: "test", + Usage: "Path to EOF validation reference test.", + } + HexFlag = &cli.StringFlag{ + Name: "hex", + Usage: "single container data parse and validation", + } +) + +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 eofDumpCommand = &cli.Command{ + Name: "eofdump", + Usage: "parses hex eof container", + Action: eofDump, + Flags: []cli.Flag{ + HexFlag, + }, +} + +func init() { + app.Commands = []*cli.Command{ + eofParserCommand, + eofDumpCommand, + } + app.Before = func(ctx *cli.Context) error { + flags.MigrateGlobalFlags(ctx) + return debug.Setup(ctx) + } + app.After = func(ctx *cli.Context) error { + debug.Exit() + return nil + } +} + +func main() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/evm/main.go b/cmd/evm/main.go index d3a1ef4faa..bec685011c 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -141,10 +141,6 @@ 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."+ @@ -157,10 +153,6 @@ var ( strings.Join(vm.ActivateableEips(), ", ")), Value: "Shanghai", } - RefTestFlag = &cli.StringFlag{ - Name: "test", - Usage: "Path to EOF validation reference test.", - } ) var stateTransitionCommand = &cli.Command{ @@ -245,17 +237,6 @@ 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() { @@ -269,7 +250,6 @@ func init() { stateTransitionCommand, transactionCommand, blockBuilderCommand, - eofParserCommand, } app.Before = func(ctx *cli.Context) error { flags.MigrateGlobalFlags(ctx) diff --git a/core/evm.go b/core/evm.go index 5d3c454d7c..c5d95d66c2 100644 --- a/core/evm.go +++ b/core/evm.go @@ -87,6 +87,9 @@ func NewEVMTxContext(msg *Message) vm.TxContext { if msg.BlobGasFeeCap != nil { ctx.BlobFeeCap = new(big.Int).Set(msg.BlobGasFeeCap) } + if msg.InitCodes != nil { + ctx.InitCodes = msg.InitCodes + } return ctx } diff --git a/core/state/state_object.go b/core/state/state_object.go index e90d3a5d05..97eca20030 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -564,9 +564,13 @@ func (s *stateObject) CodeSize() int { if len(s.code) != 0 { return len(s.code) } - if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { + codeHash := s.CodeHash() + if bytes.Equal(codeHash, types.EmptyCodeHash.Bytes()) { return 0 } + if bytes.Equal(codeHash, types.EmptyEOFCodeHash.Bytes()) { + return 2 + } size, err := s.db.db.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash())) if err != nil { s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err)) diff --git a/core/state/statedb.go b/core/state/statedb.go index dd88ba9799..71e13fe7d1 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -473,6 +473,13 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) { } } +func (s *StateDB) SetCodeEOF(addr common.Address, code []byte) { + stateObject := s.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetCode(types.EmptyEOFCodeHash, code) + } +} + func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { stateObject := s.getOrNewStateObject(addr) if stateObject != nil { diff --git a/core/state_transition.go b/core/state_transition.go index 88e6d38871..49375c72b1 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -148,6 +148,7 @@ type Message struct { BlobGasFeeCap *big.Int BlobHashes []common.Hash AuthList types.AuthorizationList + InitCodes [][]byte // When SkipAccountChecks is true, the message nonce is not checked against the // account nonce in state. It also disables checking that the sender is an EOA. @@ -171,6 +172,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In SkipAccountChecks: false, BlobHashes: tx.BlobHashes(), BlobGasFeeCap: tx.BlobGasFeeCap(), + InitCodes: tx.InitCodes(), } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { @@ -422,8 +424,17 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { contractCreation = msg.To == nil ) + // Add the initcode data for calculation of the intrinsic gas + // TODO (MariusVanDerWijden): while this should work, it is very + // dirty, better to pass the initcodes directly to IntrinsicGas + // and duplicate the cost accounting logic there. + data := msg.Data + for _, initcode := range msg.InitCodes { + data = append(data, initcode...) + } + // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.AuthList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) + gas, err := IntrinsicGas(data, msg.AccessList, msg.AuthList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) if err != nil { return nil, err } diff --git a/core/types/hashes.go b/core/types/hashes.go index cbd197072e..eed3c0433d 100644 --- a/core/types/hashes.go +++ b/core/types/hashes.go @@ -32,6 +32,9 @@ var ( // EmptyCodeHash is the known hash of the empty EVM bytecode. EmptyCodeHash = crypto.Keccak256Hash(nil) // c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 + // EmptyEOFCodeHash is the known hash of the an empty EOF bytecode. + EmptyEOFCodeHash = crypto.Keccak256Hash([]byte{0xFE, 00}) // 9dbf3648db8210552e9c4f75c6a1c3057c0ca432043bd648be15fe7be05646f5 + // EmptyTxsHash is the known hash of the empty transaction set. EmptyTxsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index 7493b72712..c4d28f197e 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -426,7 +426,6 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { return err } } - case SetCodeTxType: var itx SetCodeTx inner = &itx diff --git a/core/vm/analysis.go b/core/vm/analysis.go index d8870def53..8e707268fa 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -139,7 +139,7 @@ func eofCodeBitmapInternal(code, bits bitvec) bitvec { switch { case op >= PUSH1 && op <= PUSH32: numbits = uint8(op - PUSH1 + 1) - case op == RJUMP || op == RJUMPI || op == CALLF: + case op == RJUMP || op == RJUMPI || op == CALLF || op == JUMPF || op == DATALOADN: numbits = 2 case op == RJUMPV: // RJUMPV is unique as it has a variable sized operand. @@ -159,6 +159,8 @@ func eofCodeBitmapInternal(code, bits bitvec) bitvec { // as possible. numbits = uint8(end - pc) } + case op == DUPN || op == SWAPN || op == EXCHANGE || op == EOFCREATE || op == RETURNCONTRACT: + numbits = 1 default: // Op had no immediate operand, continue. continue diff --git a/core/vm/eips.go b/core/vm/eips.go index fd89c92d9f..d147a25fda 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -17,6 +17,7 @@ package vm import ( + "errors" "fmt" "math" "sort" @@ -25,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -570,11 +572,30 @@ func enableEOF(jt *JumpTable) { maxStack: maxStack(0, 0), undefined: true, } + jt[CALL] = undefined jt[CALLCODE] = undefined + jt[DELEGATECALL] = undefined + jt[STATICCALL] = undefined jt[SELFDESTRUCT] = undefined jt[JUMP] = undefined jt[JUMPI] = undefined jt[PC] = undefined + jt[CREATE] = undefined + jt[CREATE2] = undefined + jt[CODESIZE] = undefined + jt[CODECOPY] = undefined + jt[EXTCODESIZE] = undefined + jt[EXTCODECOPY] = undefined + jt[EXTCODEHASH] = undefined + jt[GAS] = undefined + // Allow 0xFE to terminate sections + jt[INVALID] = &operation{ + execute: opUndefined, + constantGas: 0, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + terminal: true, + } // New opcodes jt[RJUMP] = &operation{ @@ -582,25 +603,28 @@ func enableEOF(jt *JumpTable) { constantGas: GasQuickStep, minStack: minStack(0, 0), maxStack: maxStack(0, 0), - terminal: true, + immediate: 2, } jt[RJUMPI] = &operation{ execute: opRjumpi, constantGas: GasFastishStep, minStack: minStack(1, 0), maxStack: maxStack(1, 0), + immediate: 2, } jt[RJUMPV] = &operation{ execute: opRjumpv, constantGas: GasFastishStep, minStack: minStack(1, 0), maxStack: maxStack(1, 0), + immediate: 3, // at least 3, maybe more } jt[CALLF] = &operation{ execute: opCallf, constantGas: GasFastStep, minStack: minStack(0, 0), maxStack: maxStack(0, 0), + immediate: 2, } jt[RETF] = &operation{ execute: opRetf, @@ -609,6 +633,143 @@ func enableEOF(jt *JumpTable) { maxStack: maxStack(0, 0), terminal: true, } + jt[JUMPF] = &operation{ + execute: opJumpf, + constantGas: GasFastStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + immediate: 2, + terminal: true, + } + jt[EOFCREATE] = &operation{ + execute: opEOFCreate, + constantGas: params.Create2Gas, + dynamicGas: gasEOFCreate, + minStack: minStack(4, 1), + maxStack: maxStack(4, 1), + memorySize: memoryEOFCreate, + immediate: 1, + } + jt[TXCREATE] = &operation{ + execute: opTXCreate, + constantGas: params.Create2Gas, + dynamicGas: gasEOFCreate, + minStack: minStack(5, 1), + maxStack: maxStack(5, 1), + memorySize: memoryEOFCreate, + } + jt[RETURNCONTRACT] = &operation{ + execute: opReturnContract, + constantGas: GasZeroStep, + dynamicGas: memoryCopierGas(1), + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + immediate: 1, + memorySize: memoryReturnContract, + terminal: true, + } + jt[DATALOAD] = &operation{ + execute: opDataLoad, + constantGas: GasFastishStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + jt[DATALOADN] = &operation{ + execute: opDataLoadN, + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + immediate: 2, + } + jt[DATASIZE] = &operation{ + execute: opDataSize, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } + jt[DATACOPY] = &operation{ + execute: opDataCopy, + constantGas: GasFastestStep, + dynamicGas: memoryCopierGas(2), + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryMcopy, + } + jt[DUPN] = &operation{ + execute: opDupN, + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + immediate: 1, + } + jt[SWAPN] = &operation{ + execute: opSwapN, + constantGas: GasFastestStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + immediate: 1, + } + jt[EXCHANGE] = &operation{ + execute: opExchange, + constantGas: GasFastestStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + immediate: 1, + } + jt[RETURNDATALOAD] = &operation{ + execute: opReturnDataLoad, + constantGas: GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + jt[EXTCALL] = &operation{ + execute: opExtCall, + constantGas: params.CallGasEIP150, + dynamicGas: gasCall, + minStack: minStack(4, 1), + maxStack: maxStack(4, 1), + memorySize: memoryCall, + } + jt[EXTDELEGATECALL] = &operation{ + execute: opExtDelegateCall, + dynamicGas: gasDelegateCall, + constantGas: params.CallGasEIP150, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + memorySize: memoryDelegateCall, + } + jt[EXTSTATICCALL] = &operation{ + execute: opExtStaticCall, + constantGas: params.CallGasEIP150, + dynamicGas: gasStaticCall, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + memorySize: memoryStaticCall, + } +} + +func opExtCodeCopyEOF(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + stack = scope.Stack + a = stack.pop() + memOffset = stack.pop() + codeOffset = stack.pop() + length = stack.pop() + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = math.MaxUint64 + } + addr := common.Address(a.Bytes20()) + code := interpreter.evm.StateDB.GetCode(addr) + // Check if we're copying an EOF contract + if len(code) >= 2 && code[0] == 0xEF && code[1] == 0x00 { + code = []byte{0xEF, 0x00} + } + codeCopy := getData(code, uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + + return nil, nil } // opRjump implements the rjump opcode. @@ -691,3 +852,314 @@ func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt return nil, nil >>>>>>> 3532f16eb7 (core/vm: add eof container) } + +func opJumpf(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[idx] + ) + if scope.Stack.len()+int(typ.MaxStackHeight)-int(typ.Input) > 1024 { + return nil, fmt.Errorf("stack overflow") + } + scope.CodeSection = uint64(idx) + *pc = 0 + return nil, nil +} + +func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + idx = code[*pc+1] + value = scope.Stack.pop() + salt = scope.Stack.pop() + offset, size = scope.Stack.pop(), scope.Stack.pop() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = scope.Contract.Gas + ) + if int(idx) >= len(scope.Contract.Container.ContainerSections) { + return nil, fmt.Errorf("invalid subcontainer") + } + subcontainer := scope.Contract.Container.ContainerSections[idx] + + // Reuse last popped value from stack + stackvalue := size + // Apply EIP150 + gas -= gas / 64 + scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2) + + res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, subcontainer.MarshalBinary(), gas, &value, &salt) + if suberr != nil { + stackvalue.Clear() + } else { + stackvalue.SetBytes(addr.Bytes()) + } + scope.Stack.push(&stackvalue) + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + if suberr == ErrExecutionReverted { + interpreter.returnData = res // set REVERT data to return data buffer + return res, nil + } + interpreter.returnData = nil // clear dirty return data buffer + return nil, nil +} + +func opTXCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + var ( + value = scope.Stack.pop() + salt = scope.Stack.pop() + offset, size = scope.Stack.pop(), scope.Stack.pop() + txInitCodeHash = scope.Stack.pop() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = scope.Contract.Gas + ) + // Reuse last popped value from stack + stackvalue := txInitCodeHash + + var initCode []byte + for _, code := range interpreter.evm.InitCodes { + if interpreter.hasher == nil { + interpreter.hasher = crypto.NewKeccakState() + } else { + interpreter.hasher.Reset() + } + interpreter.hasher.Write(code) + interpreter.hasher.Read(interpreter.hasherBuf[:]) + if interpreter.hasherBuf.Cmp(txInitCodeHash.Bytes32()) == 0 { + initCode = code + } + } + if len(initCode) == 0 { + stackvalue.Clear() + scope.Stack.push(&stackvalue) + } + + // Additional hashing charge + hashingCharge := params.InitCodeWordGas * ((uint64(len(initCode)) + 31) / 32) + scope.Contract.UseGas(hashingCharge, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2) + // Apply EIP150 + gas -= gas / 64 + scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2) + scope.InitCodeMode = true + res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, initCode, gas, &value, &salt) + if suberr != nil { + stackvalue.Clear() + } else { + stackvalue.SetBytes(addr.Bytes()) + } + scope.Stack.push(&stackvalue) + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + if suberr == ErrExecutionReverted { + interpreter.returnData = res // set REVERT data to return data buffer + return res, nil + } + interpreter.returnData = nil // clear dirty return data buffer + return nil, nil +} + +func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if !scope.InitCodeMode { + return nil, errors.New("returncontract in non-initcode mode") + } + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + idx = code[*pc+1] + offset = scope.Stack.pop() + size = scope.Stack.pop() + ) + if int(idx) >= len(scope.Contract.Container.ContainerSections) { + return nil, fmt.Errorf("invalid subcontainer") + } + ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) + containerCode := scope.Contract.Container.ContainerCode[idx] + deployedCode := append(containerCode, ret...) + if len(deployedCode) == 0 { + return nil, errors.New("nonexistant subcontainer") + } + return deployedCode, errStopToken +} + +func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + stackItem = scope.Stack.pop() + offset, overflow = stackItem.Uint64WithOverflow() + ) + + if overflow { + stackItem.Clear() + scope.Stack.push(&stackItem) + } else { + data := getData(scope.Contract.Container.Data, offset, 32) + scope.Stack.push(stackItem.SetBytes(data)) + } + return nil, nil +} + +func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + offset = uint64(binary.BigEndian.Uint16(code[*pc+1:])) + ) + data := getData(scope.Contract.Container.Data, offset, 32) + scope.Stack.push(new(uint256.Int).SetBytes(data)) + *pc += 2 + return nil, nil +} + +func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + length := len(scope.Contract.Container.Data) + item := uint256.NewInt(uint64(length)) + scope.Stack.push(item) + return nil, nil +} + +func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + memOffset = scope.Stack.pop() + offset = scope.Stack.pop() + size = scope.Stack.pop() + ) + // These values are checked for overflow during memory expansion calculation + // (the memorySize function on the opcode). + data := getData(scope.Contract.Container.Data, offset.Uint64(), size.Uint64()) + scope.Memory.Set(memOffset.Uint64(), size.Uint64(), data) + return nil, nil +} + +func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + index = int(code[*pc+1]) + 1 + ) + scope.Stack.dup(index) + *pc += 1 + return nil, nil +} + +func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + index = int(code[*pc+1]) + 1 + ) + scope.Stack.swap(index) + *pc += 1 + return nil, nil +} + +func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + index = int(code[*pc+1]) + 1 + n = index>>4 + 1 + m = index%0x0F + 1 + ) + scope.Stack.swapN(n, m) + *pc += 1 + return nil, nil +} + +func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + offset = scope.Stack.pop() + ) + if offset.Uint64()+32 > uint64(len(interpreter.returnData)) { + return nil, errors.New("return buffer overflow") + } + scope.Stack.push(offset.SetBytes(interpreter.returnData[offset.Uint64() : offset.Uint64()+32])) + return nil, nil +} + +func opExtCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + stack := scope.Stack + // Use all available gas + gas := (scope.Contract.Gas / 64) * 63 + // Pop other call parameters. + addr, value, inOffset, inSize := stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + // safe a memory alloc + temp := addr + // Get the arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + if interpreter.readOnly && !value.IsZero() { + return nil, ErrWriteProtection + } + if !value.IsZero() { + gas += params.CallStipend + } + ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, &value) + + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.push(&temp) + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + interpreter.returnData = ret + return ret, nil +} + +func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + stack := scope.Stack + // Use all available gas + gas := (scope.Contract.Gas / 64) * 63 + // Pop other call parameters. + addr, inOffset, inSize := stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + // safe a memory alloc + temp := addr + // Get arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas, true) + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.push(&temp) + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + interpreter.returnData = ret + return ret, nil +} + +func opExtStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + stack := scope.Stack + // Use all available gas + gas := (scope.Contract.Gas / 64) * 63 + // Pop other call parameters. + addr, inOffset, inSize := stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + // safe a memory alloc + temp := addr + // Get arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas) + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.push(&temp) + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + interpreter.returnData = ret + return ret, nil +} diff --git a/core/vm/eof.go b/core/vm/eof.go index 269721e7e2..e968683d3d 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -19,6 +19,7 @@ package vm import ( "bytes" "encoding/binary" + "encoding/hex" "errors" "fmt" "io" @@ -38,7 +39,7 @@ const ( eof1Version = 1 maxInputItems = 127 - maxOutputItems = 127 + maxOutputItems = 128 maxStackHeight = 1023 maxContainerSections = 256 ) @@ -56,7 +57,7 @@ var ( 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") + ErrInvalidSection0Type = errors.New("invalid section 0 type, input and output should be zero and non-returning (0x80)") ErrTooLargeMaxStackHeight = errors.New("invalid type content, max stack height exceeds limit") ErrInvalidContainerSize = errors.New("invalid container size") ) @@ -83,8 +84,10 @@ func isEOFVersion1(code []byte) bool { type Container struct { Types []*FunctionMetadata Code [][]byte - ContainerSections [][]byte + ContainerSections []*Container + ContainerCode [][]byte Data []byte + DataSize int // might be less than len(Data) } // FunctionMetadata is an EOF function signature. @@ -109,15 +112,18 @@ func (c *Container) MarshalBinary() []byte { for _, code := range c.Code { b = binary.BigEndian.AppendUint16(b, uint16(len(code))) } + var encodedContainer [][]byte if len(c.ContainerSections) != 0 { b = append(b, kindContainer) b = binary.BigEndian.AppendUint16(b, uint16(len(c.ContainerSections))) for _, section := range c.ContainerSections { - b = binary.BigEndian.AppendUint16(b, uint16(len(section))) + encoded := section.MarshalBinary() + b = binary.BigEndian.AppendUint16(b, uint16(len(encoded))) + encodedContainer = append(encodedContainer, encoded) } } b = append(b, kindData) - b = binary.BigEndian.AppendUint16(b, uint16(len(c.Data))) + b = binary.BigEndian.AppendUint16(b, uint16(c.DataSize)) b = append(b, 0) // terminator // Write section contents. @@ -127,7 +133,7 @@ func (c *Container) MarshalBinary() []byte { for _, code := range c.Code { b = append(b, code...) } - for _, section := range c.ContainerSections { + for _, section := range encodedContainer { b = append(b, section...) } b = append(b, c.Data...) @@ -180,20 +186,18 @@ func (c *Container) UnmarshalBinary(b []byte) error { return fmt.Errorf("%w: mismatch of code sections cound and type signatures, types %d, code %d", ErrInvalidCodeSize, typesSize/4, len(codeSizes)) } - // Parse container section header. + // Parse (optional) container section header. + var containerSizes []int offset := offsetCodeKind + 2 + 2*len(codeSizes) + 1 - kind, containerSizes, err := parseSectionList(b, offset) - if err != nil { - return err - } - // The container section is optional, only unmarshal if container section is set. - if kind == kindContainer { + if offset < len(b) && b[offset] == kindContainer { + kind, containerSizes, err = parseSectionList(b, offset) + if err != nil { + return err + } + if kind != kindContainer { + panic("somethings wrong") + } offset = offset + 2 + 2*len(containerSizes) + 1 - } else { - // empty out falsly parsed container sizes - // TODO (MariusVanDerWijden): clean this up, read the kind first before parsing the section list - // and if the kind is not KindContainer, just ignore it. - containerSizes = make([]int, 0) } // Parse data section header. @@ -204,11 +208,12 @@ func (c *Container) UnmarshalBinary(b []byte) error { if kind != kindData { return fmt.Errorf("%w: found section %x instead", ErrMissingDataHeader, kind) } + c.DataSize = dataSize // Check for terminator. offsetTerminator := offset + 3 if len(b) < offsetTerminator { - return io.ErrUnexpectedEOF + return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF) } if b[offsetTerminator] != 0 { return fmt.Errorf("%w: have %x", ErrMissingTerminator, b[offsetTerminator]) @@ -219,7 +224,7 @@ func (c *Container) UnmarshalBinary(b []byte) error { if len(containerSizes) != 0 { expectedSize += sum(containerSizes) } - if len(b) != expectedSize { + if len(b) < expectedSize-dataSize || len(b) > expectedSize { return fmt.Errorf("%w: have %d, want %d", ErrInvalidContainerSize, len(b), expectedSize) } @@ -243,7 +248,7 @@ func (c *Container) UnmarshalBinary(b []byte) error { } types = append(types, sig) } - if types[0].Input != 0 || types[0].Output != 0 { + if types[0].Input != 0 || types[0].Output != 0x80 { return fmt.Errorf("%w: have %d, %d", ErrInvalidSection0Type, types[0].Input, types[0].Output) } c.Types = types @@ -265,19 +270,29 @@ func (c *Container) UnmarshalBinary(b []byte) error { if len(containerSizes) > maxContainerSections { return fmt.Errorf("%w number of container section exceed: %v: have %v", ErrInvalidContainerSectionSize, maxContainerSections, len(containerSizes)) } - container := make([][]byte, len(containerSizes)) + containerCode := make([][]byte, 0, len(containerSizes)) + container := make([]*Container, 0, len(containerSizes)) for i, size := range containerSizes { - if size == 0 { + if size == 0 || idx+size > len(b) { return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidContainerSectionSize, i) } - container[i] = b[idx : idx+size] + c := new(Container) + end := min(idx+size, len(b)) + if err := c.UnmarshalBinary(b[idx:end]); err != nil { + return fmt.Errorf("%w for section %d", err, i) + } + container = append(container, c) + containerCode = append(containerCode, b[idx:end]) + idx += size } c.ContainerSections = container + c.ContainerCode = containerCode } // Parse data section. - c.Data = b[idx : idx+dataSize] + end := min(idx+dataSize, len(b)) + c.Data = b[idx:end] return nil } @@ -285,8 +300,40 @@ func (c *Container) UnmarshalBinary(b []byte) error { // 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 { + visited := make(map[int]struct{}) + toVisit := []int{0} + for len(toVisit) > 0 { + // TODO check if this can be used as a DOS + // Theres and edge case here where we mark something as visited that we visit before, + // This should not trigger a re-visit + // e.g. 0 -> 1, 2, 3 + // 1 -> 2, 3 + // should not mean 2 and 3 should be visited twice + var ( + index = toVisit[0] + code = c.Code[index] + ) + if _, ok := visited[index]; !ok { + v, err := validateCode(code, index, c, jt) + if err != nil { + return err + } + visited[index] = struct{}{} + // Mark all sections that can be visited from here. + for idx := range v { + if _, ok := visited[idx]; !ok { + toVisit = append(toVisit, idx) + } + } + } + toVisit = toVisit[1:] + } + // Make sure every code section is visited at least once. + if len(visited) != len(c.Code) { + return ErrUnreachableCode + } + for _, container := range c.ContainerSections { + if err := container.ValidateCode(jt); err != nil { return err } } @@ -361,3 +408,42 @@ func sum(list []int) (s int) { } return } + +func (c *Container) String() string { + var result string + result += "Header\n" + result += "-----------\n" + result += fmt.Sprintf("EOFMagic: %02x\n", eofMagic) + result += fmt.Sprintf("EOFVersion: %02x\n", eof1Version) + result += fmt.Sprintf("KindType: %02x\n", kindTypes) + result += fmt.Sprintf("TypesSize: %04x\n", len(c.Types)*4) + result += fmt.Sprintf("KindCode: %02x\n", kindCode) + result += fmt.Sprintf("CodeSize: %04x\n", len(c.Code)) + for i, code := range c.Code { + result += fmt.Sprintf("Code %v length: %04x\n", i, len(code)) + } + if len(c.ContainerSections) != 0 { + result += fmt.Sprintf("KindContainer: %02x\n", kindContainer) + result += fmt.Sprintf("ContainerSize: %04x\n", len(c.ContainerSections)) + for i, section := range c.ContainerSections { + result += fmt.Sprintf("Container %v length: %04x\n", i, len(section.MarshalBinary())) + } + } + result += fmt.Sprintf("KindData: %02x\n", kindData) + result += fmt.Sprintf("DataSize: %04x\n", len(c.Data)) + result += fmt.Sprintf("Terminator: %02x\n", 0x0) + result += "-----------\n" + result += "Body\n" + result += "-----------\n" + for i, typ := range c.Types { + result += fmt.Sprintf("Type %v: %v\n", i, hex.EncodeToString([]byte{typ.Input, typ.Output, byte(typ.MaxStackHeight >> 8), byte(typ.MaxStackHeight & 0x00ff)})) + } + for i, code := range c.Code { + result += fmt.Sprintf("Code %v: %v\n", i, hex.EncodeToString(code)) + } + for i, section := range c.ContainerSections { + result += fmt.Sprintf("Section %v: %v\n", i, hex.EncodeToString(section.MarshalBinary())) + } + result += fmt.Sprintf("Data: %v\n", hex.EncodeToString(c.Data)) + return result +} diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 6adf0450b1..df79430497 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -17,6 +17,8 @@ package vm import ( + "encoding/hex" + "fmt" "reflect" "testing" @@ -30,23 +32,24 @@ func TestEOFMarshaling(t *testing.T) { }{ { want: Container{ - Types: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}}, - Code: [][]byte{common.Hex2Bytes("604200")}, - Data: []byte{0x01, 0x02, 0x03}, + Types: []*FunctionMetadata{{Input: 0, Output: 0x80, MaxStackHeight: 1}}, + Code: [][]byte{common.Hex2Bytes("604200")}, + Data: []byte{0x01, 0x02, 0x03}, + DataSize: 3, }, }, { want: Container{ - Types: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}}, - Code: [][]byte{common.Hex2Bytes("604200")}, - ContainerSections: [][]byte{common.Hex2Bytes("604200")}, - Data: []byte{0x01, 0x02, 0x03}, + Types: []*FunctionMetadata{{Input: 0, Output: 0x80, MaxStackHeight: 1}}, + Code: [][]byte{common.Hex2Bytes("604200")}, + Data: []byte{0x01, 0x02, 0x03}, + DataSize: 3, }, }, { want: Container{ Types: []*FunctionMetadata{ - {Input: 0, Output: 0, MaxStackHeight: 1}, + {Input: 0, Output: 0x80, MaxStackHeight: 1}, {Input: 2, Output: 3, MaxStackHeight: 4}, {Input: 1, Output: 1, MaxStackHeight: 1}, }, @@ -72,3 +75,45 @@ func TestEOFMarshaling(t *testing.T) { } } } + +func TestEOFSubcontainer(t *testing.T) { + var subcontainer = new(Container) + if err := subcontainer.UnmarshalBinary(common.Hex2Bytes("ef000101000402000100010400000000800000fe")); err != nil { + t.Fatal(err) + } + container := Container{ + Types: []*FunctionMetadata{{Input: 0, Output: 0x80, MaxStackHeight: 1}}, + Code: [][]byte{common.Hex2Bytes("604200")}, + ContainerSections: []*Container{subcontainer}, + Data: []byte{0x01, 0x02, 0x03}, + DataSize: 3, + } + var ( + b = container.MarshalBinary() + got Container + ) + if err := got.UnmarshalBinary(b); err != nil { + t.Fatal(err) + } + fmt.Print(got) + if res := got.MarshalBinary(); !reflect.DeepEqual(res, b) { + t.Fatalf("invalid marshalling, want %v got %v", b, res) + } +} + +func TestMarshaling(t *testing.T) { + tests := []string{ + "EF000101000402000100040400000000800000E0000000", + "ef0001010004020001000d04000000008000025fe100055f5fe000035f600100", + } + for i, test := range tests { + s, err := hex.DecodeString(test) + if err != nil { + t.Fatalf("test %d: error decoding: %v", i, err) + } + var got Container + if err := got.UnmarshalBinary(s); err != nil { + t.Fatalf("test %d: got error %v", i, err) + } + } +} diff --git a/core/vm/errors.go b/core/vm/errors.go index ef3d285a21..429ab1415b 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -41,6 +41,7 @@ var ( ErrInvalidEOF = errors.New("invalid eof") ErrInvalidEOFInitcode = errors.New("invalid eof initcode") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrInvalidNumberOfOutputs = errors.New("invalid number of outputs") // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. diff --git a/core/vm/evm.go b/core/vm/evm.go index fee0668c31..17b900bf09 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -95,6 +95,7 @@ type TxContext struct { BlobHashes []common.Hash // Provides information for BLOBHASH BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set AccessEvents *state.AccessEvents // Capture all state accesses for this tx + InitCodes [][]byte // Provides information for TXCREATE } // EVM is the Ethereum Virtual Machine base object and provides @@ -245,7 +246,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // 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, evm.parseContainer(code)) - ret, err = evm.interpreter.Run(contract, input, false) + ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } } @@ -310,7 +311,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, witness.AddCode(evm.StateDB.ResolveCode(addrCopy)) } contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code)) - ret, err = evm.interpreter.Run(contract, input, false) + ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } if err != nil { @@ -331,7 +332,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // // DelegateCall differs from CallCode in the sense that it executes the given address' // code with the caller as context and the caller is set to the caller of the caller. -func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { +func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64, fromEOF bool) (ret []byte, leftOverGas uint64, err error) { // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { // NOTE: caller must, at all times be a contract. It should never happen @@ -355,6 +356,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by } else { addrCopy := addr code := evm.StateDB.GetCode(addrCopy) + if fromEOF && !hasEOFMagic(code) { + return nil, gas, errors.New("extDelegateCall to non-eof contract") + } // 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 { @@ -362,7 +366,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by witness.AddCode(evm.StateDB.ResolveCode(addrCopy)) } contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code, evm.parseContainer(code)) - ret, err = evm.interpreter.Run(contract, input, false) + ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } if err != nil { @@ -425,7 +429,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // 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. - ret, err = evm.interpreter.Run(contract, input, true) + ret, err = evm.interpreter.Run(contract, input, true, false) gas = contract.Gas } if err != nil { @@ -479,7 +483,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Validate initcode per EOF rules. If caller is EOF and initcode is legacy, fail. isInitcodeEOF := hasEOFMagic(codeAndHash.code) - if evm.chainRules.IsShanghai { + if evm.chainRules.IsPrague { if isInitcodeEOF { // If the initcode is EOF, verify it is well-formed. var c Container @@ -548,7 +552,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } if err == nil { - ret, err = evm.interpreter.Run(contract, nil, false) + ret, err = evm.interpreter.Run(contract, nil, false, contract.IsDeployment) } // Check whether the max code size has been exceeded, assign err if the case. @@ -632,6 +636,13 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment * return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, isEOF) } +// EOFCreate +func (evm *EVM) EOFCreate(caller ContractRef, code []byte, subcontainer []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { + codeAndHash := &codeAndHash{code: subcontainer} + contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) + return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, true) +} + // ChainConfig returns the environment's chain configuration func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } diff --git a/core/vm/gas.go b/core/vm/gas.go index 5fe589bce6..def6280427 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -22,6 +22,7 @@ import ( // Gas costs const ( + GasZeroStep uint64 = 0 GasQuickStep uint64 = 2 GasFastestStep uint64 = 3 GasFastishStep uint64 = 4 diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index d294324b08..5dc36e15d2 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -502,3 +502,23 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } return gas, nil } + +func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + size, overflow := stack.Back(2).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + if size > params.MaxInitCodeSize { + return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size) + } + // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow + moreGas := (params.Keccak256WordGas) * ((size + 31) / 32) + if gas, overflow = math.SafeAdd(gas, moreGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 160ed6fc2f..43624fc127 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -828,7 +828,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext // Get arguments from the memory. args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) - ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas) + ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas, false) if err != nil { temp.Clear() } else { diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 9831b84cb0..60fd58c1b7 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -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, 0, nil}) + opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) 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, 0, nil}) + opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) 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, 0, nil}) + opFn(&pc, interpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) 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, 0, nil} + scope = &ScopeContext{nil, stack, nil, 0, nil, false} 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, 0, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) 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, 0, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) 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, 0, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) } } @@ -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, 0, nil} + scopeContext = ScopeContext{mem, stack, contract, 0, nil, false} 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, 0, nil}) + opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) } } @@ -734,7 +734,7 @@ func TestRandom(t *testing.T) { pc = uint64(0) evmInterpreter = env.interpreter ) - opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil}) + opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) 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, 0, nil}) + opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) 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, 0, nil}) + opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) 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) diff --git a/core/vm/interface.go b/core/vm/interface.go index 6079ca7847..74f9b2a5fa 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -43,6 +43,7 @@ type StateDB interface { GetCodeHash(common.Address) common.Hash GetCode(common.Address) []byte SetCode(common.Address, []byte) + SetCodeEOF(common.Address, []byte) GetCodeSize(common.Address) int ResolveCodeHash(common.Address) common.Hash diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 13867ca072..85450556f9 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -43,8 +43,9 @@ type ScopeContext struct { Stack *Stack Contract *Contract - CodeSection uint64 - ReturnStack []*ReturnContext + CodeSection uint64 + ReturnStack []*ReturnContext + InitCodeMode bool } type ReturnContext struct { @@ -161,7 +162,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { // It's important to note that any errors returned by the interpreter should be // considered a revert-and-consume-all-gas operation except for // ErrExecutionReverted which means revert-and-keep-gas-left. -func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { +func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, isInitCode bool) (ret []byte, err error) { // Increment the call depth which is restricted to 1024 in.evm.depth++ defer func() { in.evm.depth-- }() @@ -188,11 +189,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( mem = NewMemory() // bound memory stack = newstack() // local stack callContext = &ScopeContext{ - Memory: mem, - Stack: stack, - Contract: contract, - CodeSection: 0, - ReturnStack: []*ReturnContext{{Section: 0, Pc: 0, StackHeight: 0}}, + Memory: mem, + Stack: stack, + Contract: contract, + CodeSection: 0, + ReturnStack: []*ReturnContext{{Section: 0, Pc: 0, StackHeight: 0}}, + InitCodeMode: isInitCode, } // 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 diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 2d01dfaeac..b2cd8f7444 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -48,6 +48,8 @@ type operation struct { // terminal denotes if the instruction can be the final opcode in a code section terminal bool + // immediate denotes how many immediate bytes the operation uses + immediate int } var ( @@ -628,192 +630,224 @@ func newFrontierInstructionSet() JumpTable { constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 1, }, PUSH2: { execute: makePush(2, 2), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 2, }, PUSH3: { execute: makePush(3, 3), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 3, }, PUSH4: { execute: makePush(4, 4), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 4, }, PUSH5: { execute: makePush(5, 5), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 5, }, PUSH6: { execute: makePush(6, 6), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 6, }, PUSH7: { execute: makePush(7, 7), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 7, }, PUSH8: { execute: makePush(8, 8), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 8, }, PUSH9: { execute: makePush(9, 9), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 9, }, PUSH10: { execute: makePush(10, 10), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 10, }, PUSH11: { execute: makePush(11, 11), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 11, }, PUSH12: { execute: makePush(12, 12), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 12, }, PUSH13: { execute: makePush(13, 13), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 13, }, PUSH14: { execute: makePush(14, 14), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 14, }, PUSH15: { execute: makePush(15, 15), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 15, }, PUSH16: { execute: makePush(16, 16), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 16, }, PUSH17: { execute: makePush(17, 17), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 17, }, PUSH18: { execute: makePush(18, 18), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 18, }, PUSH19: { execute: makePush(19, 19), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 19, }, PUSH20: { execute: makePush(20, 20), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 20, }, PUSH21: { execute: makePush(21, 21), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 21, }, PUSH22: { execute: makePush(22, 22), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 22, }, PUSH23: { execute: makePush(23, 23), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 23, }, PUSH24: { execute: makePush(24, 24), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 24, }, PUSH25: { execute: makePush(25, 25), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 25, }, PUSH26: { execute: makePush(26, 26), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 26, }, PUSH27: { execute: makePush(27, 27), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 27, }, PUSH28: { execute: makePush(28, 28), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 28, }, PUSH29: { execute: makePush(29, 29), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 29, }, PUSH30: { execute: makePush(30, 30), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 30, }, PUSH31: { execute: makePush(31, 31), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 31, }, PUSH32: { execute: makePush(32, 32), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), + immediate: 32, }, DUP1: { execute: makeDup(1), diff --git a/core/vm/memory_table.go b/core/vm/memory_table.go index 61a910a03d..f34e7da589 100644 --- a/core/vm/memory_table.go +++ b/core/vm/memory_table.go @@ -64,6 +64,14 @@ func memoryCreate2(stack *Stack) (uint64, bool) { return calcMemSize64(stack.Back(1), stack.Back(2)) } +func memoryEOFCreate(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(1), stack.Back(2)) +} + +func memoryReturnContract(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(1)) +} + func memoryCall(stack *Stack) (uint64, bool) { x, overflow := calcMemSize64(stack.Back(5), stack.Back(6)) if overflow { diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 243e61ce11..cca75183ee 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -121,9 +121,6 @@ const ( TLOAD OpCode = 0x5c TSTORE OpCode = 0x5d MCOPY OpCode = 0x5e - RJUMP OpCode = 0x5c - RJUMPI OpCode = 0x5d - RJUMPV OpCode = 0x5e PUSH0 OpCode = 0x5f ) @@ -212,10 +209,28 @@ const ( LOG4 ) -// 0xb0 range - control flow ops. +// 0xd0 range - eof operations. const ( - CALLF = 0xb0 - RETF = 0xb1 + DATALOAD OpCode = 0xd0 + DATALOADN OpCode = 0xd1 + DATASIZE OpCode = 0xd2 + DATACOPY OpCode = 0xd3 +) + +// 0xe0 range - eof operations. +const ( + RJUMP OpCode = 0xe0 + RJUMPI OpCode = 0xe1 + RJUMPV OpCode = 0xe2 + CALLF OpCode = 0xe3 + RETF OpCode = 0xe4 + JUMPF OpCode = 0xe5 + DUPN OpCode = 0xe6 + SWAPN OpCode = 0xe7 + EXCHANGE OpCode = 0xe8 + EOFCREATE OpCode = 0xec + TXCREATE OpCode = 0xed + RETURNCONTRACT OpCode = 0xee ) // 0xf0 range - closures. @@ -227,10 +242,15 @@ const ( DELEGATECALL OpCode = 0xf4 CREATE2 OpCode = 0xf5 - STATICCALL OpCode = 0xfa - REVERT OpCode = 0xfd - INVALID OpCode = 0xfe - SELFDESTRUCT OpCode = 0xff + RETURNDATALOAD OpCode = 0xf7 + EXTCALL OpCode = 0xf8 + EXTDELEGATECALL OpCode = 0xf9 + + STATICCALL OpCode = 0xfa + EXTSTATICCALL OpCode = 0xfb + REVERT OpCode = 0xfd + INVALID OpCode = 0xfe + SELFDESTRUCT OpCode = 0xff ) var opCodeToString = [256]string{ @@ -314,11 +334,7 @@ var opCodeToString = [256]string{ TLOAD: "TLOAD", TSTORE: "TSTORE", MCOPY: "MCOPY", - // TODO (MariusVanDerWijden) reenable once moved - //RJUMP: "RJUMP", - //RJUMPI: "RJUMPI", - //RJUMPV: "RJUMPV", - PUSH0: "PUSH0", + PUSH0: "PUSH0", // 0x60 range - pushes. PUSH1: "PUSH1", @@ -397,9 +413,25 @@ var opCodeToString = [256]string{ LOG3: "LOG3", LOG4: "LOG4", - // 0xb0 range. - CALLF: "CALLF", - RETF: "RETF", + // 0xd range - eof ops. + DATALOAD: "DATALOAD", + DATALOADN: "DATALOADN", + DATASIZE: "DATASIZE", + DATACOPY: "DATACOPY", + + // 0xe0 range. + RJUMP: "RJUMP", + RJUMPI: "RJUMPI", + RJUMPV: "RJUMPV", + CALLF: "CALLF", + RETF: "RETF", + JUMPF: "JUMPF", + DUPN: "DUPN", + SWAPN: "SWAPN", + EXCHANGE: "EXCHANGE", + EOFCREATE: "EOFCREATE", + TXCREATE: "TXCREATE", + RETURNCONTRACT: "RETURNCONTRACT", // 0xf0 range - closures. CREATE: "CREATE", @@ -408,10 +440,16 @@ var opCodeToString = [256]string{ CALLCODE: "CALLCODE", DELEGATECALL: "DELEGATECALL", CREATE2: "CREATE2", - STATICCALL: "STATICCALL", - REVERT: "REVERT", - INVALID: "INVALID", - SELFDESTRUCT: "SELFDESTRUCT", + + RETURNDATALOAD: "RETURNDATALOAD", + EXTCALL: "EXTCALL", + EXTDELEGATECALL: "EXTDELEGATECALL", + + STATICCALL: "STATICCALL", + EXTSTATICCALL: "EXTSTATICCALL", + REVERT: "REVERT", + INVALID: "INVALID", + SELFDESTRUCT: "SELFDESTRUCT", } func (op OpCode) String() string { @@ -422,155 +460,175 @@ func (op OpCode) String() string { } var stringToOp = map[string]OpCode{ - "STOP": STOP, - "ADD": ADD, - "MUL": MUL, - "SUB": SUB, - "DIV": DIV, - "SDIV": SDIV, - "MOD": MOD, - "SMOD": SMOD, - "EXP": EXP, - "NOT": NOT, - "LT": LT, - "GT": GT, - "SLT": SLT, - "SGT": SGT, - "EQ": EQ, - "ISZERO": ISZERO, - "SIGNEXTEND": SIGNEXTEND, - "AND": AND, - "OR": OR, - "XOR": XOR, - "BYTE": BYTE, - "SHL": SHL, - "SHR": SHR, - "SAR": SAR, - "ADDMOD": ADDMOD, - "MULMOD": MULMOD, - "KECCAK256": KECCAK256, - "ADDRESS": ADDRESS, - "BALANCE": BALANCE, - "ORIGIN": ORIGIN, - "CALLER": CALLER, - "CALLVALUE": CALLVALUE, - "CALLDATALOAD": CALLDATALOAD, - "CALLDATASIZE": CALLDATASIZE, - "CALLDATACOPY": CALLDATACOPY, - "CHAINID": CHAINID, - "BASEFEE": BASEFEE, - "BLOBHASH": BLOBHASH, - "BLOBBASEFEE": BLOBBASEFEE, - "DELEGATECALL": DELEGATECALL, - "STATICCALL": STATICCALL, - "CODESIZE": CODESIZE, - "CODECOPY": CODECOPY, - "GASPRICE": GASPRICE, - "EXTCODESIZE": EXTCODESIZE, - "EXTCODECOPY": EXTCODECOPY, - "RETURNDATASIZE": RETURNDATASIZE, - "RETURNDATACOPY": RETURNDATACOPY, - "EXTCODEHASH": EXTCODEHASH, - "BLOCKHASH": BLOCKHASH, - "COINBASE": COINBASE, - "TIMESTAMP": TIMESTAMP, - "NUMBER": NUMBER, - "DIFFICULTY": DIFFICULTY, - "GASLIMIT": GASLIMIT, - "SELFBALANCE": SELFBALANCE, - "POP": POP, - "MLOAD": MLOAD, - "MSTORE": MSTORE, - "MSTORE8": MSTORE8, - "SLOAD": SLOAD, - "SSTORE": SSTORE, - "JUMP": JUMP, - "JUMPI": JUMPI, - "PC": PC, - "MSIZE": MSIZE, - "GAS": GAS, - "JUMPDEST": JUMPDEST, - "TLOAD": TLOAD, - "TSTORE": TSTORE, - "MCOPY": MCOPY, - "PUSH0": PUSH0, - "PUSH1": PUSH1, - "PUSH2": PUSH2, - "PUSH3": PUSH3, - "PUSH4": PUSH4, - "PUSH5": PUSH5, - "PUSH6": PUSH6, - "PUSH7": PUSH7, - "PUSH8": PUSH8, - "PUSH9": PUSH9, - "PUSH10": PUSH10, - "PUSH11": PUSH11, - "PUSH12": PUSH12, - "PUSH13": PUSH13, - "PUSH14": PUSH14, - "PUSH15": PUSH15, - "PUSH16": PUSH16, - "PUSH17": PUSH17, - "PUSH18": PUSH18, - "PUSH19": PUSH19, - "PUSH20": PUSH20, - "PUSH21": PUSH21, - "PUSH22": PUSH22, - "PUSH23": PUSH23, - "PUSH24": PUSH24, - "PUSH25": PUSH25, - "PUSH26": PUSH26, - "PUSH27": PUSH27, - "PUSH28": PUSH28, - "PUSH29": PUSH29, - "PUSH30": PUSH30, - "PUSH31": PUSH31, - "PUSH32": PUSH32, - "DUP1": DUP1, - "DUP2": DUP2, - "DUP3": DUP3, - "DUP4": DUP4, - "DUP5": DUP5, - "DUP6": DUP6, - "DUP7": DUP7, - "DUP8": DUP8, - "DUP9": DUP9, - "DUP10": DUP10, - "DUP11": DUP11, - "DUP12": DUP12, - "DUP13": DUP13, - "DUP14": DUP14, - "DUP15": DUP15, - "DUP16": DUP16, - "SWAP1": SWAP1, - "SWAP2": SWAP2, - "SWAP3": SWAP3, - "SWAP4": SWAP4, - "SWAP5": SWAP5, - "SWAP6": SWAP6, - "SWAP7": SWAP7, - "SWAP8": SWAP8, - "SWAP9": SWAP9, - "SWAP10": SWAP10, - "SWAP11": SWAP11, - "SWAP12": SWAP12, - "SWAP13": SWAP13, - "SWAP14": SWAP14, - "SWAP15": SWAP15, - "SWAP16": SWAP16, - "LOG0": LOG0, - "LOG1": LOG1, - "LOG2": LOG2, - "LOG3": LOG3, - "LOG4": LOG4, - "CREATE": CREATE, - "CREATE2": CREATE2, - "CALL": CALL, - "RETURN": RETURN, - "CALLCODE": CALLCODE, - "REVERT": REVERT, - "INVALID": INVALID, - "SELFDESTRUCT": SELFDESTRUCT, + "STOP": STOP, + "ADD": ADD, + "MUL": MUL, + "SUB": SUB, + "DIV": DIV, + "SDIV": SDIV, + "MOD": MOD, + "SMOD": SMOD, + "EXP": EXP, + "NOT": NOT, + "LT": LT, + "GT": GT, + "SLT": SLT, + "SGT": SGT, + "EQ": EQ, + "ISZERO": ISZERO, + "SIGNEXTEND": SIGNEXTEND, + "AND": AND, + "OR": OR, + "XOR": XOR, + "BYTE": BYTE, + "SHL": SHL, + "SHR": SHR, + "SAR": SAR, + "ADDMOD": ADDMOD, + "MULMOD": MULMOD, + "KECCAK256": KECCAK256, + "ADDRESS": ADDRESS, + "BALANCE": BALANCE, + "ORIGIN": ORIGIN, + "CALLER": CALLER, + "CALLVALUE": CALLVALUE, + "CALLDATALOAD": CALLDATALOAD, + "CALLDATASIZE": CALLDATASIZE, + "CALLDATACOPY": CALLDATACOPY, + "CHAINID": CHAINID, + "BASEFEE": BASEFEE, + "BLOBHASH": BLOBHASH, + "BLOBBASEFEE": BLOBBASEFEE, + "DELEGATECALL": DELEGATECALL, + "STATICCALL": STATICCALL, + "CODESIZE": CODESIZE, + "CODECOPY": CODECOPY, + "GASPRICE": GASPRICE, + "EXTCODESIZE": EXTCODESIZE, + "EXTCODECOPY": EXTCODECOPY, + "RETURNDATASIZE": RETURNDATASIZE, + "RETURNDATACOPY": RETURNDATACOPY, + "EXTCODEHASH": EXTCODEHASH, + "BLOCKHASH": BLOCKHASH, + "COINBASE": COINBASE, + "TIMESTAMP": TIMESTAMP, + "NUMBER": NUMBER, + "DIFFICULTY": DIFFICULTY, + "GASLIMIT": GASLIMIT, + "SELFBALANCE": SELFBALANCE, + "POP": POP, + "MLOAD": MLOAD, + "MSTORE": MSTORE, + "MSTORE8": MSTORE8, + "SLOAD": SLOAD, + "SSTORE": SSTORE, + "JUMP": JUMP, + "JUMPI": JUMPI, + "PC": PC, + "MSIZE": MSIZE, + "GAS": GAS, + "JUMPDEST": JUMPDEST, + "TLOAD": TLOAD, + "TSTORE": TSTORE, + "MCOPY": MCOPY, + "PUSH0": PUSH0, + "PUSH1": PUSH1, + "PUSH2": PUSH2, + "PUSH3": PUSH3, + "PUSH4": PUSH4, + "PUSH5": PUSH5, + "PUSH6": PUSH6, + "PUSH7": PUSH7, + "PUSH8": PUSH8, + "PUSH9": PUSH9, + "PUSH10": PUSH10, + "PUSH11": PUSH11, + "PUSH12": PUSH12, + "PUSH13": PUSH13, + "PUSH14": PUSH14, + "PUSH15": PUSH15, + "PUSH16": PUSH16, + "PUSH17": PUSH17, + "PUSH18": PUSH18, + "PUSH19": PUSH19, + "PUSH20": PUSH20, + "PUSH21": PUSH21, + "PUSH22": PUSH22, + "PUSH23": PUSH23, + "PUSH24": PUSH24, + "PUSH25": PUSH25, + "PUSH26": PUSH26, + "PUSH27": PUSH27, + "PUSH28": PUSH28, + "PUSH29": PUSH29, + "PUSH30": PUSH30, + "PUSH31": PUSH31, + "PUSH32": PUSH32, + "DUP1": DUP1, + "DUP2": DUP2, + "DUP3": DUP3, + "DUP4": DUP4, + "DUP5": DUP5, + "DUP6": DUP6, + "DUP7": DUP7, + "DUP8": DUP8, + "DUP9": DUP9, + "DUP10": DUP10, + "DUP11": DUP11, + "DUP12": DUP12, + "DUP13": DUP13, + "DUP14": DUP14, + "DUP15": DUP15, + "DUP16": DUP16, + "SWAP1": SWAP1, + "SWAP2": SWAP2, + "SWAP3": SWAP3, + "SWAP4": SWAP4, + "SWAP5": SWAP5, + "SWAP6": SWAP6, + "SWAP7": SWAP7, + "SWAP8": SWAP8, + "SWAP9": SWAP9, + "SWAP10": SWAP10, + "SWAP11": SWAP11, + "SWAP12": SWAP12, + "SWAP13": SWAP13, + "SWAP14": SWAP14, + "SWAP15": SWAP15, + "SWAP16": SWAP16, + "LOG0": LOG0, + "LOG1": LOG1, + "LOG2": LOG2, + "LOG3": LOG3, + "LOG4": LOG4, + "DATALOAD": DATALOAD, + "DATALOADN": DATALOADN, + "DATASIZE": DATASIZE, + "DATACOPY": DATACOPY, + "RJUMP": RJUMP, + "RJUMPI": RJUMPI, + "RJUMPV": RJUMPV, + "CALLF": CALLF, + "RETF": RETF, + "JUMPF": JUMPF, + "DUPN": DUPN, + "SWAPN": SWAPN, + "EXCHANGE": EXCHANGE, + "EOFCREATE": EOFCREATE, + "TXCREATE": TXCREATE, + "RETURNCONTRACT": RETURNCONTRACT, + "CREATE": CREATE, + "CREATE2": CREATE2, + "RETURNDATALOAD": RETURNDATALOAD, + "EXTCALL": EXTCALL, + "EXTDELEGATECALL": EXTDELEGATECALL, + "EXTSTATICCALL": EXTSTATICCALL, + "CALL": CALL, + "RETURN": RETURN, + "CALLCODE": CALLCODE, + "REVERT": REVERT, + "INVALID": INVALID, + "SELFDESTRUCT": SELFDESTRUCT, } // StringToOp finds the opcode whose name is stored in `str`. diff --git a/core/vm/stack.go b/core/vm/stack.go index 879dc9aa6d..22358b3a54 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -113,6 +113,14 @@ func (st *Stack) swap16() { st.data[st.len()-17], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-17] } +func (st *Stack) swap(n int) { + st.swapN(n, 1) +} + +func (st *Stack) swapN(n, m int) { + st.data[st.len()-n], st.data[st.len()-m] = st.data[st.len()-m], st.data[st.len()-n] +} + func (st *Stack) dup(n int) { st.push(&st.data[st.len()-n]) } diff --git a/core/vm/validate.go b/core/vm/validate.go index fccdd6fa3e..023baa4b75 100644 --- a/core/vm/validate.go +++ b/core/vm/validate.go @@ -23,20 +23,24 @@ import ( ) 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") + ErrUndefinedInstruction = errors.New("undefined instruction") + ErrTruncatedImmediate = errors.New("truncated immediate") + ErrInvalidSectionArgument = errors.New("invalid section argument") + ErrInvalidCallArgument = errors.New("callf into non-returning section") + ErrInvalidDataloadNArgument = errors.New("invalid dataloadN argument") + ErrInvalidJumpDest = errors.New("invalid jump destination") + ErrInvalidBackwardJump = errors.New("invalid backward jump") + 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") + ErrEOFCreateWithTruncatedSection = errors.New("eofcreate with truncated section") + 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 { +func validateCode(code []byte, section int, container *Container, jt *JumpTable) (map[int]struct{}, error) { var ( i = 0 // Tracks the number of actual instructions in the code (e.g. @@ -45,6 +49,7 @@ func validateCode(code []byte, section int, metadata []*FunctionMetadata, jt *Ju count = 0 op OpCode analysis bitvec + visited = make(map[int]struct{}) ) // This loop visits every single instruction and verifies: // * if the instruction is valid for the given jump table. @@ -56,64 +61,81 @@ func validateCode(code []byte, section int, metadata []*FunctionMetadata, jt *Ju count++ op = OpCode(code[i]) if jt[op].undefined { - return fmt.Errorf("%w: op %s, pos %d", ErrUndefinedInstruction, op, i) + return visited, fmt.Errorf("%w: op %s, pos %d", ErrUndefinedInstruction, op, i) } - switch { - case op >= PUSH1 && op <= PUSH32: - size := int(op - PUSH0) + if size := jt[op].immediate; size != 0 { if len(code) <= i+size { - return fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i) + return visited, 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 + switch { + case op == RJUMP || op == RJUMPI: + if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil { + return visited, err + } + case op == RJUMPV: + max_size := int(code[i+1]) + length := max_size + 1 + if len(code) <= i+length { + return visited, fmt.Errorf("%w: jump table truncated, op %s, pos %d", ErrTruncatedImmediate, op, i) + } + offset := i + 2 + for j := 0; j < length; j++ { + if err := checkDest(code, &analysis, offset+j*2, offset+(length*2), len(code)); err != nil { + return visited, err + } + } + i += 2 * max_size + case op == CALLF: + arg, _ := parseUint16(code[i+1:]) + if arg >= len(container.Types) { + return visited, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidSectionArgument, arg, len(container.Types), i) + } + if container.Types[arg].Output == 0x80 { + return visited, fmt.Errorf("%w: section %v", ErrInvalidCallArgument, arg) + } + visited[arg] = struct{}{} + case op == JUMPF: + arg, _ := parseUint16(code[i+1:]) + if arg >= len(container.Types) { + return visited, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidSectionArgument, arg, len(container.Types), i) + } + visited[arg] = struct{}{} + case op == DATALOADN: + arg, _ := parseUint16(code[i+1:]) + if arg+32 > len(container.Data) { + return visited, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidDataloadNArgument, arg, len(container.Data), i) + } + case op == RETURNCONTRACT: + arg := int(code[i+1]) + if arg >= len(container.ContainerSections) { + return visited, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.ContainerSections), i) + } + case op == EOFCREATE: + arg := int(code[i+1]) + if arg >= len(container.ContainerSections) { + return visited, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.ContainerSections), i) + } + if ct := container.ContainerSections[arg]; len(ct.Data) != ct.DataSize { + return visited, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", ErrEOFCreateWithTruncatedSection, arg, len(ct.Data), ct.DataSize, i) } } - 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 += size } 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) + // Therefore, the last instruction must be considered terminal or RJUMP. + if !jt[op].terminal && op != RJUMP { + return visited, fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, i) } - if paths, err := validateControlFlow(code, section, metadata, jt); err != nil { - return err + if paths, err := validateControlFlow2(code, section, container.Types, jt); err != nil { + return visited, err } else if paths != count { - // TODO(matt): return actual position of unreacable code - return ErrUnreachableCode + fmt.Printf("Paths: %v Count: %v\n", paths, count) + // TODO(matt): return actual position of unreachable code + return visited, ErrUnreachableCode } - return nil + return visited, nil } // checkDest parses a relative offset at code[0:2] and checks if it is a valid jump destination. @@ -139,32 +161,37 @@ func checkDest(code []byte, analysis *bitvec, imm, from, length int) error { // 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 + pos int + height int + backwards bool } var ( heights = make(map[int]int) - worklist = []item{{0, int(metadata[section].Input)}} + worklist = []item{{0, int(metadata[section].Input), false}} maxStackHeight = int(metadata[section].Input) ) for 0 < len(worklist) { var ( - idx = len(worklist) - 1 - pos = worklist[idx].pos - height = worklist[idx].height + idx = len(worklist) - 1 + pos = worklist[idx].pos + height = worklist[idx].height + backwards = worklist[idx].backwards ) 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) + if height == want { + // Already visited this path and stack height + // matches. + break } - // Already visited this path and stack height - // matches. + // Already visited this path but the stack height is not the same, need to revisit again + // TODO (MariusVanDerWijden): can this result in an infinite loop? + } else if backwards { + // If a instruction can only be reached by backwards jump, bail break } heights[pos] = height @@ -181,14 +208,15 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata, switch { case op == CALLF: arg, _ := parseUint16(code[pos+1:]) - if want, have := int(metadata[arg].Input), height; want > have { + newSection := metadata[arg] + if want, have := int(newSection.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 { + if have, limit := height+int(newSection.MaxStackHeight)-int(newSection.Input), 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) + height -= int(newSection.Input) + height += int(newSection.Output) pos += 3 case op == RETF: if have, want := int(metadata[section].Output), height; have != want { @@ -198,26 +226,61 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata, case op == RJUMP: arg := parseInt16(code[pos+1:]) pos += 3 + arg + worklist = append(worklist, item{pos: pos, height: height, backwards: arg < 0}) + break outer 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]) + count := int(code[pos+1]) + 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 + case op == DUPN: + fallthrough + case op == SWAPN: + arg := int(code[pos+1]) + 1 + if want, have := arg, height; want >= have { + return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) + } + pos += 2 + case op == EXCHANGE: + arg := int(code[pos+1]) + n := arg>>4 + 1 + m := arg&0x0f + 1 + if want, have := n+m, height; want >= have { + return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) + } + pos += 2 + case op == JUMPF: + arg, _ := parseUint16(code[pos+1:]) + newSection := metadata[arg] + if have, limit := height+int(newSection.MaxStackHeight)-int(newSection.Input), int(params.StackLimit); have > limit { + return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos) + } + if newSection.Output == 0x80 { + if want, have := int(newSection.Input), height; want > have { + return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) + } + } else { + if have, want := height, int(metadata[section].Output)+int(newSection.Input)-int(newSection.Output); have != want { + return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNumberOfOutputs, pos) + } + } + pos += 3 default: - if op >= PUSH1 && op <= PUSH32 { - pos += 1 + int(op-PUSH0) - } else if jt[op].terminal { - break outer + if jt[op].immediate != 0 { + pos += jt[op].immediate + 1 } else { // Simple op, no operand. pos += 1 } + if jt[op].terminal { + break outer + } } maxStackHeight = max(maxStackHeight, height) } diff --git a/core/vm/validate_linear.go b/core/vm/validate_linear.go new file mode 100644 index 0000000000..bfd4b4e2e0 --- /dev/null +++ b/core/vm/validate_linear.go @@ -0,0 +1,204 @@ +package vm + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/params" +) + +type bounds struct { + min int + max int +} + +func validateControlFlow2(code []byte, section int, metadata []*FunctionMetadata, jt *JumpTable) (int, error) { + var ( + stackBounds = make(map[int]*bounds) + maxStackHeight = int(metadata[section].Input) + debugging = !true + ) + + setBounds := func(pos, min, maxi int) *bounds { + stackBounds[pos] = &bounds{min, maxi} + maxStackHeight = max(maxStackHeight, maxi) + return stackBounds[pos] + } + // set the initial stack bounds + setBounds(0, int(metadata[section].Input), int(metadata[section].Input)) + + for pos := 0; pos < len(code); pos++ { + op := OpCode(code[pos]) + currentBounds := stackBounds[pos] + if currentBounds == nil { + fmt.Printf("Stack bounds not set: %v at %v \n", op, pos) + return 0, ErrUnreachableCode + } + + if debugging { + fmt.Println(pos, op, maxStackHeight, currentBounds) + } + + var ( + currentStackMax = currentBounds.max + currentStackMin = currentBounds.min + ) + + switch op { + case CALLF: + arg, _ := parseUint16(code[pos+1:]) + newSection := metadata[arg] + if want, have := int(newSection.Input), currentBounds.min; want > have { + return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) + } + if have, limit := currentBounds.max+int(newSection.MaxStackHeight)-int(newSection.Input), int(params.StackLimit); have > limit { + return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos) + } + change := int(newSection.Output) - int(newSection.Input) + currentStackMax += change + currentStackMin += change + case RETF: + if currentBounds.max != currentBounds.min { + return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidNumberOfOutputs, currentBounds.max, currentBounds.min, pos) + } + if have, want := int(metadata[section].Output), currentBounds.min; have != want { + return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", ErrInvalidOutputs, have, want, pos) + } + case JUMPF: + arg, _ := parseUint16(code[pos+1:]) + newSection := metadata[arg] + if have, limit := currentBounds.max+int(newSection.MaxStackHeight)-int(newSection.Input), int(params.StackLimit); have > limit { + return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos) + } + if newSection.Output == 0x80 { + if want, have := int(newSection.Input), currentBounds.min; want > have { + return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) + } + } else { + if currentBounds.max != currentBounds.min { + return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidNumberOfOutputs, currentBounds.max, currentBounds.min, pos) + } + if have, want := currentBounds.max, int(metadata[section].Output)+int(newSection.Input)-int(newSection.Output); have != want { + return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNumberOfOutputs, pos) + } + } + case DUPN: + arg := int(code[pos+1]) + 1 + if want, have := arg, currentBounds.min; want > have { + return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) + } + case SWAPN: + arg := int(code[pos+1]) + 1 + if want, have := arg+1, currentBounds.min; want > have { + return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) + } + case EXCHANGE: + arg := int(code[pos+1]) + n := arg>>4 + 1 + m := arg&0x0f + 1 + if want, have := n+m+1, currentBounds.min; want > have { + return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) + } + default: + if want, have := jt[op].minStack, currentBounds.min; want > have { + return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) + } + } + + if !jt[op].terminal && op != CALLF { + change := int(params.StackLimit) - jt[op].maxStack + currentStackMax += change + currentStackMin += change + } + + var next []int + switch op { + case RJUMP: + nextPos := pos + 2 + parseInt16(code[pos+1:]) + next = append(next, nextPos) + // We set the stack bounds of the destination + // and skip the argument + if nextPos+1 < pos { + nextBounds, ok := stackBounds[nextPos+1] + if !ok { + return 0, ErrInvalidBackwardJump + } + if nextBounds.max != currentStackMax || nextBounds.min != currentStackMin { + return 0, ErrInvalidMaxStackHeight + } + } + setBounds(next[0]+1, currentStackMin, currentStackMax) + case RJUMPI: + arg := parseInt16(code[pos+1:]) + next = append(next, pos+2) + next = append(next, pos+2+arg) + case RJUMPV: + count := int(code[pos+1]) + 1 + next = append(next, pos+1+2*count) + for i := 0; i < count; i++ { + arg := parseInt16(code[pos+2+2*i:]) + next = append(next, pos+1+2*count+arg) + } + default: + if jt[op].immediate != 0 { + next = append(next, pos+jt[op].immediate) + } else { + // Simple op, no operand. + next = append(next, pos) + } + } + if debugging { + fmt.Println(next) + } + + if op != RJUMP && !jt[op].terminal { + for _, instr := range next { + nextPC := instr + 1 + if nextPC >= len(code) { + return 0, fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, pos) + } + nextOP := code[nextPC] + if nextPC > pos { + // target reached via forward jump or seq flow + nextBounds, ok := stackBounds[nextPC] + if !ok { + setBounds(nextPC, currentStackMin, currentStackMax) + } else { + setBounds(nextPC, min(nextBounds.min, currentStackMin), max(nextBounds.max, currentStackMax)) + } + } else { + // target reached via backwards jump + nextBounds, ok := stackBounds[nextPC] + if !ok { + return 0, ErrInvalidBackwardJump + } + change := int(params.StackLimit) - jt[nextOP].maxStack + jt[nextOP].minStack + if have, want := nextBounds.max+change, currentBounds.max; have != want { + fmt.Println(nextPC, have, want, change) + return 0, fmt.Errorf("%w want %d as max got %d at pos %d,", ErrInvalidBackwardJump, want, have, pos) + } + if have, want := nextBounds.min+change, currentBounds.min; have != want { + return 0, fmt.Errorf("%w want %d as min got %d at pos %d,", ErrInvalidBackwardJump, want, have, pos) + } + if currentStackMax != nextBounds.max { + return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", ErrInvalidBackwardJump, currentStackMax, nextBounds.max, pos) + } + } + } + } + + if op == RJUMP { + pos += 2 // skip the immediate + } else { + pos = next[0] + } + + } + if maxStackHeight >= int(params.StackLimit) { + return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)} + } + if maxStackHeight != int(metadata[section].MaxStackHeight) { + fmt.Print(maxStackHeight, metadata[section].MaxStackHeight) + return 0, fmt.Errorf("%w in code section %d: have %d, want %d", ErrInvalidMaxStackHeight, section, maxStackHeight, metadata[section].MaxStackHeight) + } + return len(stackBounds), nil +} diff --git a/core/vm/validate_test.go b/core/vm/validate_test.go index b2e706e61d..4d80117a65 100644 --- a/core/vm/validate_test.go +++ b/core/vm/validate_test.go @@ -17,10 +17,12 @@ package vm import ( + "encoding/binary" "errors" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" ) func TestValidateCode(t *testing.T) { @@ -118,7 +120,7 @@ func TestValidateCode(t *testing.T) { code: []byte{ byte(PUSH0), byte(RJUMPV), - byte(0x02), + byte(0x01), byte(0x00), byte(0x01), byte(0x00), @@ -141,24 +143,25 @@ func TestValidateCode(t *testing.T) { }, section: 0, metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}}, - err: ErrInvalidBranchCount, + err: ErrTruncatedImmediate, }, { code: []byte{ byte(RJUMP), 0x00, 0x03, - byte(JUMPDEST), + byte(JUMPDEST), // this code is unreachable to forward jumps alone byte(JUMPDEST), byte(RETURN), byte(PUSH1), 20, byte(PUSH1), 39, byte(PUSH1), 0x00, - byte(CODECOPY), + byte(DATACOPY), byte(PUSH1), 20, byte(PUSH1), 0x00, byte(RJUMP), 0xff, 0xef, }, section: 0, metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 3}}, + err: ErrUnreachableCode, }, { code: []byte{ @@ -170,7 +173,7 @@ func TestValidateCode(t *testing.T) { byte(PUSH1), 20, byte(PUSH1), 39, byte(PUSH1), 0x00, - byte(CODECOPY), + byte(DATACOPY), byte(PUSH1), 20, byte(PUSH1), 0x00, byte(RETURN), @@ -181,14 +184,14 @@ func TestValidateCode(t *testing.T) { { code: []byte{ byte(PUSH1), 1, - byte(RJUMPV), 0x02, 0x00, 0x03, 0xff, 0xf8, + byte(RJUMPV), 0x01, 0x00, 0x03, 0xff, 0xf8, byte(JUMPDEST), byte(JUMPDEST), byte(STOP), byte(PUSH1), 20, byte(PUSH1), 39, byte(PUSH1), 0x00, - byte(CODECOPY), + byte(DATACOPY), byte(PUSH1), 20, byte(PUSH1), 0x00, byte(RETURN), @@ -242,9 +245,252 @@ func TestValidateCode(t *testing.T) { metadata: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 2}, {Input: 2, Output: 1, MaxStackHeight: 2}}, }, } { - err := validateCode(test.code, test.section, test.metadata, &pragueEOFInstructionSet) + container := &Container{ + Types: test.metadata, + Data: make([]byte, 0), + ContainerSections: make([]*Container, 0), + } + _, err := validateCode(test.code, test.section, container, &pragueEOFInstructionSet) 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) } } } + +func BenchmarkRJUMPI(b *testing.B) { + snippet := []byte{ + byte(PUSH0), + byte(RJUMPI), 0xFF, 0xFC, + } + code := []byte{} + for i := 0; i < params.MaxCodeSize/len(snippet)-1; i++ { + code = append(code, snippet...) + } + code = append(code, byte(STOP)) + container := &Container{ + Types: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}}, + Data: make([]byte, 0), + ContainerSections: make([]*Container, 0), + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := validateCode(code, 0, container, &pragueEOFInstructionSet) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkRJUMPV(b *testing.B) { + snippet := []byte{ + byte(PUSH0), + byte(RJUMPV), + 0xff, // count + 0x00, 0x00, + } + for i := 0; i < 255; i++ { + snippet = append(snippet, []byte{0x00, 0x00}...) + } + code := []byte{} + for i := 0; i < 24576/len(snippet)-1; i++ { + code = append(code, snippet...) + } + code = append(code, byte(PUSH0)) + code = append(code, byte(STOP)) + container := &Container{ + Types: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}}, + Data: make([]byte, 0), + ContainerSections: make([]*Container, 0), + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := validateCode(code, 0, container, &pragueEOFInstructionSet) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEOFValidation(b *testing.B) { + var container Container + var code []byte + maxSections := 1024 + for i := 0; i < maxSections; i++ { + code = append(code, byte(CALLF)) + code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1) + } + code = append(code, byte(STOP)) + // First container + container.Code = append(container.Code, code) + container.Types = append(container.Types, &FunctionMetadata{Input: 0, Output: 0x80, MaxStackHeight: 0}) + + inner := []byte{ + byte(STOP), + } + + for i := 0; i < 1023; i++ { + container.Code = append(container.Code, inner) + container.Types = append(container.Types, &FunctionMetadata{Input: 0, Output: 0, MaxStackHeight: 0}) + } + + for i := 0; i < 12; i++ { + container.Code[i+1] = code + } + + bin := container.MarshalBinary() + if len(bin) > 48*1024 { + b.Fatal("Exceeds 48Kb") + } + + var container2 Container + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := container2.UnmarshalBinary(bin); err != nil { + b.Fatal(err) + } + if err := container2.ValidateCode(&pragueEOFInstructionSet); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEOFValidation2(b *testing.B) { + var container Container + var code []byte + maxSections := 1024 + for i := 0; i < maxSections; i++ { + code = append(code, byte(CALLF)) + code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1) + } + code = append(code, byte(STOP)) + // First container + container.Code = append(container.Code, code) + container.Types = append(container.Types, &FunctionMetadata{Input: 0, Output: 0x80, MaxStackHeight: 0}) + + inner := []byte{ + byte(CALLF), 0x03, 0xE8, + byte(CALLF), 0x03, 0xE9, + byte(CALLF), 0x03, 0xF0, + byte(CALLF), 0x03, 0xF1, + byte(CALLF), 0x03, 0xF2, + byte(CALLF), 0x03, 0xF3, + byte(CALLF), 0x03, 0xF4, + byte(CALLF), 0x03, 0xF5, + byte(CALLF), 0x03, 0xF6, + byte(CALLF), 0x03, 0xF7, + byte(CALLF), 0x03, 0xF8, + byte(JUMPF), 0x00, 0x00, + } + + for i := 0; i < 1023; i++ { + container.Code = append(container.Code, inner) + container.Types = append(container.Types, &FunctionMetadata{Input: 0, Output: 0, MaxStackHeight: 0}) + } + + bin := container.MarshalBinary() + if len(bin) > 48*1024 { + b.Fatal("Exceeds 48Kb") + } + + var container2 Container + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := container2.UnmarshalBinary(bin); err != nil { + b.Fatal(err) + } + if err := container2.ValidateCode(&pragueEOFInstructionSet); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEOFValidation3(b *testing.B) { + var container Container + var code []byte + snippet := []byte{ + byte(PUSH0), + byte(RJUMPV), + 0xff, // count + 0x00, 0x00, + } + for i := 0; i < 255; i++ { + snippet = append(snippet, []byte{0x00, 0x00}...) + } + code = append(code, snippet...) + maxSections := 1024 + for i := 0; i < maxSections; i++ { + code = append(code, byte(CALLF)) + code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1) + } + code = append(code, byte(STOP)) + // First container + container.Code = append(container.Code, code) + container.Types = append(container.Types, &FunctionMetadata{Input: 0, Output: 0x80, MaxStackHeight: 1}) + + for i := 0; i < 1023; i++ { + container.Code = append(container.Code, []byte{byte(RJUMP), 0x00, 0x00, byte(JUMPF), 0x00, 0x00}) + container.Types = append(container.Types, &FunctionMetadata{Input: 0, Output: 0, MaxStackHeight: 0}) + } + for i := 0; i < 65; i++ { + container.Code[i+1] = append(snippet, byte(STOP)) + container.Types[i+1] = &FunctionMetadata{Input: 0, Output: 0, MaxStackHeight: 1} + } + bin := container.MarshalBinary() + if len(bin) > 48*1024 { + b.Fatal("Exceeds 48Kb") + } + b.ResetTimer() + b.ReportMetric(float64(len(bin)), "bytes") + for i := 0; i < b.N; i++ { + for k := 0; k < 40; k++ { + var container2 Container + if err := container2.UnmarshalBinary(bin); err != nil { + b.Fatal(err) + } + if err := container2.ValidateCode(&pragueEOFInstructionSet); err != nil { + b.Fatal(err) + } + } + } +} + +func BenchmarkRJUMPI_2(b *testing.B) { + code := []byte{ + byte(PUSH0), + byte(RJUMPI), 0xFF, 0xFC, + } + for i := 0; i < params.MaxCodeSize/4-1; i++ { + code = append(code, byte(PUSH0)) + x := -4 * i + code = append(code, byte(RJUMPI)) + code = binary.BigEndian.AppendUint16(code, uint16(x)) + } + code = append(code, byte(STOP)) + container := &Container{ + Types: []*FunctionMetadata{{Input: 0, Output: 0, MaxStackHeight: 1}}, + Data: make([]byte, 0), + ContainerSections: make([]*Container, 0), + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := validateCode(code, 0, container, &pragueEOFInstructionSet) + if err != nil { + b.Fatal(err) + } + } +} + +func FuzzUnmarshalBinary(f *testing.F) { + f.Fuzz(func(_ *testing.T, input []byte) { + var container Container + container.UnmarshalBinary(input) + }) +} + +func FuzzValidate(f *testing.F) { + f.Fuzz(func(_ *testing.T, code []byte, maxStack uint16) { + var container Container + container.Types = append(container.Types, &FunctionMetadata{Input: 0, Output: 0x80, MaxStackHeight: maxStack}) + validateCode(code, 0, &container, &pragueEOFInstructionSet) + }) +} diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 7122b3c90e..616e923ae4 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -77,7 +77,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller()) tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig()) - ret, err := env.Interpreter().Run(contract, []byte{}, false) + ret, err := env.Interpreter().Run(contract, []byte{}, false, false) tracer.OnExit(0, ret, startGas-contract.Gas, err, true) // Rest gas assumes no refund tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index 137608f884..29308cec43 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -62,7 +62,7 @@ func TestStoreCapture(t *testing.T) { contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} var index common.Hash logger.OnTxStart(env.GetVMContext(), nil, common.Address{}) - _, err := env.Interpreter().Run(contract, []byte{}, false) + _, err := env.Interpreter().Run(contract, []byte{}, false, false) if err != nil { t.Fatal(err) } diff --git a/params/protocol_params.go b/params/protocol_params.go index 273bb3161c..dcd0592938 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -130,8 +130,9 @@ const ( DefaultElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have. InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks. - MaxCodeSize = 24576 // Maximum bytecode to permit for a contract - MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions + MaxCodeSize = 24576 // Maximum bytecode to permit for a contract. + MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions. + MaxInitCodeCount = 256 // Maximum number of initcodes in an initcode transaction. // Precompiled contract gas prices