diff --git a/go/interpreter/lfvm/converter.go b/go/interpreter/lfvm/converter.go index 4870ac5d7..a0b1a9796 100644 --- a/go/interpreter/lfvm/converter.go +++ b/go/interpreter/lfvm/converter.go @@ -29,18 +29,18 @@ type ConversionConfig struct { // Positive values larger than 0 but less than maxCachedCodeLength are // reported as invalid cache sizes during initialization. CacheSize int - // WithSuperInstructions enables the use of super instructions. - WithSuperInstructions bool + // SuperInstructionsEnabled mark the use of super instructions. + SuperInstructionsEnabled bool } -// Converter converts EVM code to LFVM code. -type Converter struct { +// converter converts EVM code to LFVM code. +type converter struct { config ConversionConfig cache *lru.Cache[tosca.Hash, Code] } // NewConverter creates a new code converter with the provided configuration. -func NewConverter(config ConversionConfig) (*Converter, error) { +func NewConverter(config ConversionConfig) (*converter, error) { if config.CacheSize == 0 { config.CacheSize = (1 << 30) // = 1GiB } @@ -55,7 +55,7 @@ func NewConverter(config ConversionConfig) (*Converter, error) { return nil, err } } - return &Converter{ + return &converter{ config: config, cache: cache, }, nil @@ -64,7 +64,7 @@ func NewConverter(config ConversionConfig) (*Converter, error) { // Convert converts EVM code to LFVM code. If the provided code hash is not nil, // it is assumed to be a valid hash of the code and is used to cache the // conversion result. If the hash is nil, the conversion result is not cached. -func (c *Converter) Convert(code []byte, codeHash *tosca.Hash) Code { +func (c *converter) Convert(code []byte, codeHash *tosca.Hash) Code { if c.cache == nil || codeHash == nil { return convert(code, c.config) } @@ -165,7 +165,7 @@ func convertWithObserver( // Convert instructions observer(i, res.nextPos) - inc := appendInstructions(&res, i, code, options.WithSuperInstructions) + inc := appendInstructions(&res, i, code, options.SuperInstructionsEnabled) i += inc + 1 } return res.toCode() diff --git a/go/interpreter/lfvm/converter_test.go b/go/interpreter/lfvm/converter_test.go index 3e06f03be..ed84ec2f2 100644 --- a/go/interpreter/lfvm/converter_test.go +++ b/go/interpreter/lfvm/converter_test.go @@ -267,7 +267,7 @@ func TestConvert_ProgramCounterBeyond16bitAreConvertedIntoInvalidInstructions(t func TestConvert_BaseInstructionsAreConvertedToEquivalents(t *testing.T) { config := ConversionConfig{ - WithSuperInstructions: false, + SuperInstructionsEnabled: false, } for _, op := range allOpCodesWhere(OpCode.isBaseInstruction) { t.Run(op.String(), func(t *testing.T) { @@ -372,7 +372,7 @@ func TestConvert_AllJumpToOperationsPointToSubsequentJumpdest(t *testing.T) { func TestConvert_SI_WhenEnabledSuperInstructionsAreUsed(t *testing.T) { config := ConversionConfig{ - WithSuperInstructions: true, + SuperInstructionsEnabled: true, } for _, op := range allOpCodesWhere(OpCode.isSuperInstruction) { t.Run(op.String(), func(t *testing.T) { @@ -393,7 +393,7 @@ func TestConvert_SI_WhenEnabledSuperInstructionsAreUsed(t *testing.T) { func TestConvert_SI_WhenDisabledNoSuperInstructionsAreUsed(t *testing.T) { config := ConversionConfig{ - WithSuperInstructions: false, + SuperInstructionsEnabled: false, } for _, op := range allOpCodesWhere(OpCode.isSuperInstruction) { t.Run(op.String(), func(t *testing.T) { diff --git a/go/interpreter/lfvm/ct.go b/go/interpreter/lfvm/ct.go index 01d7eb77b..b3e4374c0 100644 --- a/go/interpreter/lfvm/ct.go +++ b/go/interpreter/lfvm/ct.go @@ -24,9 +24,7 @@ import ( ) func NewConformanceTestingTarget() ct.Evm { - converter, err := NewConverter(ConversionConfig{ - WithSuperInstructions: false, - }) + converter, err := NewConverter(DefaultConfiguration().ConversionConfig) if err != nil { panic("failed to create converter: " + err.Error()) } @@ -38,7 +36,7 @@ func NewConformanceTestingTarget() ct.Evm { } type ctAdapter struct { - converter *Converter + converter *converter pcMapCache *lru.Cache[[32]byte, *pcMap] } @@ -135,7 +133,7 @@ func genPcMap(code []byte) *pcMap { lfvmToEvm := make([]uint16, len(code)+1) config := ConversionConfig{ - WithSuperInstructions: false, + SuperInstructionsEnabled: false, } res := convertWithObserver(code, config, func(evm, lfvm int) { evmToLfvm[evm] = uint16(lfvm) diff --git a/go/interpreter/lfvm/ct_test.go b/go/interpreter/lfvm/ct_test.go index 54ceccae3..2c8896b11 100644 --- a/go/interpreter/lfvm/ct_test.go +++ b/go/interpreter/lfvm/ct_test.go @@ -317,7 +317,7 @@ func TestConvertToLfvm_CodeWithSuperInstructions(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - options := ConversionConfig{WithSuperInstructions: true} + options := ConversionConfig{SuperInstructionsEnabled: true} got := convert(test.evmCode, options) if !reflect.DeepEqual(test.want, got) { t.Fatalf("unexpected code, wanted %v, got %v", test.want, got) diff --git a/go/interpreter/lfvm/instruction_logger_test.go b/go/interpreter/lfvm/instruction_logger_test.go index 8ccc688fb..6ba8fb961 100644 --- a/go/interpreter/lfvm/instruction_logger_test.go +++ b/go/interpreter/lfvm/instruction_logger_test.go @@ -55,9 +55,7 @@ func TestInterpreter_Logger_ExecutesCodeAndLogs(t *testing.T) { code := test.code buffer := bytes.NewBuffer([]byte{}) logger := newLogger(buffer) - config := interpreterConfig{ - runner: logger, - } + config := DefaultConfiguration().WithRunner(logger) _, err := run(config, params, code) if err != nil { t.Errorf("unexpected error: %v", err) @@ -88,9 +86,7 @@ func TestInterpreter_Logger_RunsWithoutOutput(t *testing.T) { defer func() { os.Stderr = oldErr }() logger := newLogger(nil) - config := interpreterConfig{ - runner: logger, - } + config := DefaultConfiguration().WithRunner(logger) _, err := run(config, params, code) if err != nil { @@ -132,9 +128,7 @@ func (l loggerErrorMock) Write(p []byte) (n int, err error) { func TestInterpreter_logger_PropagatesWriterError(t *testing.T) { logger := newLogger(loggerErrorMock{}) - config := interpreterConfig{ - runner: logger, - } + config := DefaultConfiguration().WithRunner(logger) // Get tosca.Parameters params := tosca.Parameters{} code := []Instruction{{STOP, 0}} diff --git a/go/interpreter/lfvm/instruction_statistcs_test.go b/go/interpreter/lfvm/instruction_statistcs_test.go index ffabc8bac..23a2e9137 100644 --- a/go/interpreter/lfvm/instruction_statistcs_test.go +++ b/go/interpreter/lfvm/instruction_statistcs_test.go @@ -32,7 +32,7 @@ func TestStatisticsRunner_RunWithStatistics(t *testing.T) { statsRunner := &statisticRunner{ stats: newStatistics(), } - config := interpreterConfig{ + config := Configuration{ runner: statsRunner, } _, err := run(config, params, code) @@ -84,9 +84,7 @@ func TestStatisticsRunner_DumpProfilePrintsExpectedOutput(t *testing.T) { stats: newStatistics(), } - instance, err := newVm(config{ - runner: statsRunner, - }) + instance, err := NewInterpreter(DefaultConfiguration().WithRunner(statsRunner)) if err != nil { t.Fatalf("Failed to create VM: %v", err) } diff --git a/go/interpreter/lfvm/interpreter.go b/go/interpreter/lfvm/interpreter.go index de3b149b4..9a8000e82 100644 --- a/go/interpreter/lfvm/interpreter.go +++ b/go/interpreter/lfvm/interpreter.go @@ -83,13 +83,8 @@ type runner interface { run(*context) (status, error) } -type interpreterConfig struct { - withShaCache bool - runner runner -} - func run( - config interpreterConfig, + config Configuration, params tosca.Parameters, code Code, ) (tosca.Result, error) { @@ -110,7 +105,7 @@ func run( stack: NewStack(), memory: NewMemory(), code: code, - withShaCache: config.withShaCache, + withShaCache: config.ShaCache, } defer ReturnStack(ctxt.stack) diff --git a/go/interpreter/lfvm/interpreter_test.go b/go/interpreter/lfvm/interpreter_test.go index da47554b9..bba8cf65e 100644 --- a/go/interpreter/lfvm/interpreter_test.go +++ b/go/interpreter/lfvm/interpreter_test.go @@ -284,7 +284,7 @@ func TestInterpreter_Vanilla_RunsWithoutOutput(t *testing.T) { os.Stdout = w // Run testing code - _, err := run(interpreterConfig{}, params, code) + _, err := run(Configuration{}, params, code) // read the output _ = w.Close() // ignore error in test out, _ := io.ReadAll(r) @@ -302,7 +302,7 @@ func TestInterpreter_Vanilla_RunsWithoutOutput(t *testing.T) { func TestInterpreter_EmptyCodeBypassesRunnerAndSucceeds(t *testing.T) { code := []Instruction{} params := tosca.Parameters{} - config := interpreterConfig{ + config := Configuration{ runner: NewMockrunner(gomock.NewController(t)), } @@ -321,7 +321,7 @@ func TestInterpreter_run_ReturnsErrorOnRuntimeError(t *testing.T) { runner := NewMockrunner(gomock.NewController(t)) code := []Instruction{{JUMPDEST, 0}} params := tosca.Parameters{Gas: 20} - config := interpreterConfig{runner: runner} + config := Configuration{runner: runner} expectedError := fmt.Errorf("runtime error") @@ -716,7 +716,7 @@ func benchmarkFib(b *testing.B, arg int, with_super_instructions bool) { example := getFibExample() // Convert example to LFVM format. - converted := convert(example.code, ConversionConfig{WithSuperInstructions: with_super_instructions}) + converted := convert(example.code, ConversionConfig{SuperInstructionsEnabled: with_super_instructions}) // Create input data. diff --git a/go/interpreter/lfvm/lfvm.go b/go/interpreter/lfvm/lfvm.go index ad331549c..07939ef95 100644 --- a/go/interpreter/lfvm/lfvm.go +++ b/go/interpreter/lfvm/lfvm.go @@ -17,27 +17,71 @@ import ( "github.com/Fantom-foundation/Tosca/go/tosca" ) -// Config provides a set of user-definable options for the LFVM interpreter. -type Config struct { +// Configuration defines the configuration for the LFVM interpreter, +// including the configuration for the conversion process. +// Use DefaultConfiguration to get the default values sanctioned for +// the lfvm use in production. +type Configuration struct { + ConversionConfig + ShaCache bool + runner runner // TODO: add options for code cache size and other configuration options + // FIXME: See ConfigurationOverrides } -// NewInterpreter creates a new LFVM interpreter instance with the official -// configuration for production purposes. -func NewInterpreter(Config) (*lfvm, error) { - return newVm(config{ +// DefaultConfiguration ist the set of configuration values sanctioned for +// the lfvm use in production. +func DefaultConfiguration() Configuration { + return Configuration{ ConversionConfig: ConversionConfig{ - WithSuperInstructions: false, + SuperInstructionsEnabled: false, }, - WithShaCache: true, - }) + ShaCache: true, + } +} + +func (c Configuration) WithSuperInstructions(enabled bool) Configuration { + c.ConversionConfig.SuperInstructionsEnabled = enabled + return c +} +func (c Configuration) WithShaCache(enabled bool) Configuration { + c.ShaCache = enabled + return c +} + +func (c Configuration) WithoutCodeCache() Configuration { + c.ConversionConfig.CacheSize = -1 + return c +} +func (c Configuration) WithCodeCacheSize(size int) Configuration { + c.ConversionConfig.CacheSize = size + return c +} + +func (c Configuration) WithRunner(r runner) Configuration { + c.runner = r + return c } -// Registers the long-form EVM as a possible interpreter implementation. +// ConfigurationOverrides defines the configuration overrides for the LFVM interpreter. +// The keys are the configuration properties to override, and the values are the new values. +// Use this object to override a sub-set of configuration values to construct a +// specific interpreter instance. +// TODO: close the set of allowed keys and its semantics, +// TODO: decide what kind of type should be used for this object. +type ConfigurationOverrides map[string]any + +func (c Configuration) WithOverrides(overrides ConfigurationOverrides) Configuration { + if v, ok := overrides["CodeCacheSize"]; ok { + c = c.WithCodeCacheSize(v.(int)) + } + return c +} + +// Registers the long-form EVM as an available interpreter implementation in the +// global registry. func init() { - tosca.MustRegisterInterpreterFactory("lfvm", func(any) (tosca.Interpreter, error) { - return NewInterpreter(Config{}) - }) + tosca.MustRegisterInterpreterFactory("lfvm", makeFactory(DefaultConfiguration())) } // RegisterExperimentalInterpreterConfigurations registers all experimental @@ -45,83 +89,67 @@ func init() { // function should not be called in production code, as the resulting VMs are // not officially supported. func RegisterExperimentalInterpreterConfigurations() error { + + configurations := map[string]Configuration{} + for _, si := range []string{"", "-si"} { for _, shaCache := range []string{"", "-no-sha-cache"} { - for _, mode := range []string{"", "-stats", "-logging"} { + for _, runner := range []string{"", "-stats", "-logging"} { - config := config{ - ConversionConfig: ConversionConfig{ - WithSuperInstructions: si == "-si", - }, - WithShaCache: shaCache != "-no-sha-cache", - } + config := DefaultConfiguration() + config = config.WithShaCache(shaCache != "-no-sha-cache") + config = config.WithSuperInstructions(si == "-si") - if mode == "-stats" { - config.runner = &statisticRunner{ + if runner == "-stats" { + config.WithRunner(&statisticRunner{ stats: newStatistics(), - } - } else if mode == "-logging" { - config.runner = loggingRunner{ + }) + } else if runner == "-logging" { + config.WithRunner(loggingRunner{ log: os.Stdout, - } + }) } - name := "lfvm" + si + shaCache + mode - if name != "lfvm" { - err := tosca.RegisterInterpreterFactory( - name, - func(any) (tosca.Interpreter, error) { - return newVm(config) - }, - ) - if err != nil { - return fmt.Errorf("failed to register interpreter %q: %v", name, err) - } + name := "lfvm" + si + shaCache + runner + if name == "lfvm" { + continue } + configurations[name] = config } } } - lfvmNoCodeCache := "lfvm-no-code-cache" - err := tosca.RegisterInterpreterFactory( - lfvmNoCodeCache, - func(any) (tosca.Interpreter, error) { - return newVm(config{ - ConversionConfig: ConversionConfig{ - CacheSize: -1, - }, - }) - }, - ) - if err != nil { - return fmt.Errorf("failed to register interpreter %q: %v", lfvmNoCodeCache, err) + configurations["lfvm-no-code-cache"] = DefaultConfiguration().WithoutCodeCache() + + for name, config := range configurations { + err := tosca.RegisterInterpreterFactory(name, makeFactory(config)) + if err != nil { + return fmt.Errorf("failed to register interpreter %q: %v", name, err) + } } return nil } -type config struct { - ConversionConfig - WithShaCache bool - runner runner -} +// newestSupportedRevision defines the newest supported revision for this interpreter implementation +const newestSupportedRevision = tosca.R13_Cancun -type lfvm struct { - config config - converter *Converter +// Interpreter is the LFVM interpreter implementation instance. +type Interpreter struct { + config Configuration + converter *converter } -func newVm(config config) (*lfvm, error) { +// NewInterpreter creates a new LFVM interpreter +func NewInterpreter(config Configuration) (*Interpreter, error) { converter, err := NewConverter(config.ConversionConfig) if err != nil { return nil, fmt.Errorf("failed to create converter: %v", err) } - return &lfvm{config: config, converter: converter}, nil + return &Interpreter{config: config, converter: converter}, nil } -// Defines the newest supported revision for this interpreter implementation -const newestSupportedRevision = tosca.R13_Cancun - -func (v *lfvm) Run(params tosca.Parameters) (tosca.Result, error) { +// Rune executes the contract described in tosca.Parameters. +func (v *Interpreter) Run(params tosca.Parameters) (tosca.Result, error) { if params.Revision > newestSupportedRevision { return tosca.Result{}, &tosca.ErrUnsupportedRevision{Revision: params.Revision} } @@ -131,22 +159,43 @@ func (v *lfvm) Run(params tosca.Parameters) (tosca.Result, error) { params.CodeHash, ) - config := interpreterConfig{ - withShaCache: v.config.WithShaCache, - runner: v.config.runner, - } - - return run(config, params, converted) + return run(v.config, params, converted) } -func (e *lfvm) DumpProfile() { +// DumpProfile prints the profile of the interpreter, if the runner supports it. +// otherwise, it does nothing. +func (e *Interpreter) DumpProfile() { if statsRunner, ok := e.config.runner.(*statisticRunner); ok { fmt.Print(statsRunner.getSummary()) } } -func (e *lfvm) ResetProfile() { +// ResetProfile resets the profile of the interpreter, if the runner supports it. +// otherwise, it does nothing. +func (e *Interpreter) ResetProfile() { if statsRunner, ok := e.config.runner.(*statisticRunner); ok { statsRunner.reset() } } + +// GetConfiguration returns a copy of the configuration of the interpreter +func (e *Interpreter) GetConfiguration() Configuration { + return e.config +} + +// makeFactory is a helper tool that returns a factory function for the LFVM interpreter. +// the reason this is a separate function is to centralize the parsing additional +// configuration overrides, which modify the default configuration of the interpreter. +func makeFactory(config Configuration) func(any) (tosca.Interpreter, error) { + return func(v any) (tosca.Interpreter, error) { + var overrides ConfigurationOverrides + if v != nil { + var ok bool + if overrides, ok = v.(ConfigurationOverrides); !ok { + return nil, fmt.Errorf("invalid configuration overrides type: %T, expected %T", v, ConfigurationOverrides{}) + } + } + + return NewInterpreter(config.WithOverrides(overrides)) + } +} diff --git a/go/interpreter/lfvm/lfvm_interface_test.go b/go/interpreter/lfvm/lfvm_interface_test.go new file mode 100644 index 000000000..c0a729fef --- /dev/null +++ b/go/interpreter/lfvm/lfvm_interface_test.go @@ -0,0 +1,62 @@ +// Copyright (c) 2024 Fantom Foundation +// +// Use of this software is governed by the Business Source License included +// in the LICENSE file and at fantom.foundation/bsl11. +// +// Change Date: 2028-4-16 +// +// On the date above, in accordance with the Business Source License, use of +// this software will be governed by the GNU Lesser General Public License v3. + +package lfvm_test + +import ( + "strings" + "testing" + + "github.com/Fantom-foundation/Tosca/go/interpreter/lfvm" + "github.com/Fantom-foundation/Tosca/go/tosca" +) + +// TestLfvm_RegisterExperimentalConfigurations tests the registration of +// experimental configurations. +// This test is slightly different from other tests because of dealing with the +// global registry: +// - It is declared in it's own package to avoid leaking the registration to other tests. +// - It tests different properties, in one single function. The reason is that the +// order of different functions may change, invalidating the test. +func TestLfvm_RegisterExperimentalConfigurations(t *testing.T) { + + // Fist registration must succeed. + err := lfvm.RegisterExperimentalInterpreterConfigurations() + if err != nil { + t.Fatalf("failed to register experimental configurations: %v", err) + } + + // Registering a second time must fail. + err = lfvm.RegisterExperimentalInterpreterConfigurations() + if err == nil { + t.Fatalf("expected error when registering experimental configurations twice") + } + + // construct all registered interpreter configurations and check their properties + for name, factory := range tosca.GetAllRegisteredInterpreters() { + t.Run(name, func(t *testing.T) { + vm, err := factory(lfvm.ConfigurationOverrides{}) + if err != nil { + t.Fatalf("failed to create interpreter: %v", err) + } + + lfvm, ok := vm.(*lfvm.Interpreter) + if !ok { + t.Fatalf("unexpected interpreter implementation, got %T", vm) + } + if lfvm.GetConfiguration().ShaCache == strings.Contains(name, "-no-sha-cache") { + t.Fatalf("%v is not configured with sha cache", name) + } + if lfvm.GetConfiguration().ConversionConfig.SuperInstructionsEnabled != strings.Contains(name, "-si") { + t.Fatalf("%v is not configured with super instructions", name) + } + }) + } +} diff --git a/go/interpreter/lfvm/lfvm_test.go b/go/interpreter/lfvm/lfvm_test.go index 22a5e56c7..7e346969a 100644 --- a/go/interpreter/lfvm/lfvm_test.go +++ b/go/interpreter/lfvm/lfvm_test.go @@ -11,21 +11,25 @@ package lfvm import ( + "fmt" "testing" "github.com/Fantom-foundation/Tosca/go/tosca" + "github.com/Fantom-foundation/Tosca/go/tosca/vm" + "go.uber.org/mock/gomock" ) func TestNewInterpreter_ProducesInstanceWithSanctionedProperties(t *testing.T) { - lfvm, err := NewInterpreter(Config{}) + lfvm, err := NewInterpreter(DefaultConfiguration()) if err != nil { t.Fatalf("failed to create LFVM instance: %v", err) } - if lfvm.config.WithShaCache != true { - t.Fatalf("LFVM is not configured with sha cache") + + if lfvm.GetConfiguration().ShaCache != true { + t.Fatalf("lfvm is not configured with sha cache") } - if lfvm.config.ConversionConfig.WithSuperInstructions != false { - t.Fatalf("LFVM is configured with super instructions") + if lfvm.GetConfiguration().ConversionConfig.SuperInstructionsEnabled != false { + t.Fatalf("lfvm is configured with super instructions") } } @@ -34,14 +38,125 @@ func TestLfvm_OfficialConfigurationHasSanctionedProperties(t *testing.T) { if err != nil { t.Fatalf("lfvm is not registered: %v", err) } - lfvm, ok := vm.(*lfvm) + lfvm, ok := vm.(*Interpreter) if !ok { t.Fatalf("unexpected interpreter implementation, got %T", vm) } - if lfvm.config.WithShaCache != true { + if lfvm.GetConfiguration().ShaCache != true { t.Fatalf("lfvm is not configured with sha cache") } - if lfvm.config.ConversionConfig.WithSuperInstructions != false { + if lfvm.GetConfiguration().ConversionConfig.SuperInstructionsEnabled != false { t.Fatalf("lfvm is configured with super instructions") } } + +func TestLFVM_Configuration_BuilderCanCreateConfigurations(t *testing.T) { + config := DefaultConfiguration() + if config.ShaCache != true { + t.Fatalf("default configuration does not have sha cache") + } + if config.ConversionConfig.SuperInstructionsEnabled != false { + t.Fatalf("default configuration has super instructions") + } + + config = config.WithShaCache(false) + if config.ShaCache != false { + t.Fatalf("configuration has sha cache") + } + + config = config.WithSuperInstructions(true) + if config.ConversionConfig.SuperInstructionsEnabled != true { + t.Fatalf("configuration does not have super instructions") + } + + config = config.WithCodeCacheSize(1000) + if config.ConversionConfig.CacheSize != 1000 { + t.Fatalf("configuration does not have code cache size 1000") + } + + config = config.WithoutCodeCache() + if config.ConversionConfig.CacheSize != -1 { + t.Fatalf("configuration does not have code cache size -1") + } +} + +func TestLFVM_Configuration_BuilderCanSetRunner(t *testing.T) { + runner := NewMockrunner(gomock.NewController(t)) + runner.EXPECT().run(gomock.Any()) + config := DefaultConfiguration().WithRunner(runner) + v, err := NewInterpreter(config) + if err != nil { + t.Fatalf("failed to create LFVM instance: %v", err) + } + + params := tosca.Parameters{ + // some code is needed to avoid zero length code bypass + Code: []byte{byte(vm.PUSH1), 0x01, byte(vm.PUSH1), 0x02, byte(vm.ADD)}, + } + _, _ = v.Run(params) +} + +func TestLFVM_NewInterpreter_InterpreterConstructionCanFail(t *testing.T) { + config := DefaultConfiguration().WithCodeCacheSize(maxCachedCodeLength / 2) + _, err := NewInterpreter(config) + if err == nil { + t.Fatalf("expected error when creating LFVM instance with small code cache") + } +} + +func TestLFVM_ReportsErrorWhenExecutingUnsupportedRevision(t *testing.T) { + v, err := NewInterpreter(DefaultConfiguration()) + if err != nil { + t.Fatalf("failed to create LFVM instance: %v", err) + } + + params := tosca.Parameters{ + Code: []byte{byte(vm.PUSH1), 0x01, byte(vm.PUSH1), 0x02, byte(vm.ADD)}, + } + params.Revision = newestSupportedRevision + 1 + _, err = v.Run(params) + + if want, got := fmt.Sprintf("unsupported revision %d", params.Revision), err.Error(); want != got { + t.Fatalf("expected error %q, got %q", want, got) + } +} + +func TestLFVM_InstanceCanBeParametrized(t *testing.T) { + + configOverrides := ConfigurationOverrides{ + "CodeCacheSize": maxCachedCodeLength * 4, + } + + registeredInterpreters := tosca.GetAllRegisteredInterpreters() + + lfvm, ok := registeredInterpreters["lfvm"] + if !ok { + t.Fatalf("lfvm is not registered") + } + instance, err := lfvm(configOverrides) + if err != nil { + t.Fatalf("failed to create interpreter: %v", err) + } + + lfvmInstance, ok := instance.(*Interpreter) + if !ok { + t.Fatalf("unexpected interpreter implementation, got %T", instance) + } + + if want, got := maxCachedCodeLength*4, lfvmInstance.GetConfiguration().ConversionConfig.CacheSize; want != got { + t.Fatalf("lfvm is not configured with code cache size %d", maxCachedCodeLength*4) + } +} + +func TestLFVM_RegisterFactoryExpectsKnownInput(t *testing.T) { + + registeredInterpreters := tosca.GetAllRegisteredInterpreters() + lfvm, ok := registeredInterpreters["lfvm"] + if !ok { + t.Fatalf("lfvm is not registered") + } + _, err := lfvm(int(54)) + if err == nil { + t.Fatalf("expected error when creating interpreter with invalid configuration overrides") + } +}