diff --git a/runtime/contract_function_executor.go b/runtime/contract_function_executor.go index e7f3cc7e24..8ba0f49bf8 100644 --- a/runtime/contract_function_executor.go +++ b/runtime/contract_function_executor.go @@ -273,6 +273,7 @@ func (executor *interpreterContractFunctionExecutor) convertArgument( inter, locationRange, environment, + environment.ResolveLocation, argument, argumentType, ) diff --git a/runtime/convertValues.go b/runtime/convertValues.go index 99ddbf18c3..0de6e52513 100644 --- a/runtime/convertValues.go +++ b/runtime/convertValues.go @@ -20,9 +20,11 @@ package runtime import ( "math/big" + "strings" _ "unsafe" "github.com/onflow/cadence" + "github.com/onflow/cadence/ast" "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" "github.com/onflow/cadence/interpreter" @@ -792,6 +794,7 @@ type valueImporter struct { inter *interpreter.Interpreter locationRange interpreter.LocationRange standardLibraryHandler stdlib.StandardLibraryHandler + resolveLocation sema.LocationHandlerFunc } // ImportValue converts a Cadence value to a runtime value. @@ -799,6 +802,7 @@ func ImportValue( inter *interpreter.Interpreter, locationRange interpreter.LocationRange, standardLibraryHandler stdlib.StandardLibraryHandler, + resolveLocation sema.LocationHandlerFunc, value cadence.Value, expectedType sema.Type, ) (interpreter.Value, error) { @@ -806,6 +810,7 @@ func ImportValue( inter: inter, locationRange: locationRange, standardLibraryHandler: standardLibraryHandler, + resolveLocation: resolveLocation, }.importValue(value, expectedType) } @@ -1509,6 +1514,33 @@ func (i valueImporter) importCompositeValue( inter := i.inter locationRange := i.locationRange + // Resolve the location if it is not nil (not a built-in type) + + if location != nil { + resolveLocation := i.resolveLocation + if resolveLocation != nil { + + rootIdentifier := strings.SplitN(qualifiedIdentifier, ".", 2)[0] + + resolvedLocations, err := resolveLocation( + []ast.Identifier{{Identifier: rootIdentifier}}, + location, + ) + if err != nil { + return nil, err + } + + if len(resolvedLocations) != 1 { + return nil, errors.NewDefaultUserError( + "cannot import value of type %s: location resolution failed", + qualifiedIdentifier, + ) + } + + location = resolvedLocations[0].Location + } + } + typeID := common.NewTypeIDFromQualifiedName(inter, location, qualifiedIdentifier) compositeType, typeErr := inter.GetCompositeType(location, qualifiedIdentifier, typeID) if typeErr != nil { diff --git a/runtime/environment.go b/runtime/environment.go index ff71d25c08..9aff5b329d 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -76,6 +76,8 @@ type Environment interface { ) CommitStorage(inter *interpreter.Interpreter) error NewAccountValue(inter *interpreter.Interpreter, address interpreter.AddressValue) interpreter.Value + + ResolveLocation(identifiers []ast.Identifier, location common.Location) ([]ResolvedLocation, error) } // interpreterEnvironmentReconfigured is the portion of interpreterEnvironment @@ -206,7 +208,7 @@ func (e *interpreterEnvironment) newCheckerConfig() *sema.Config { BaseValueActivationHandler: e.getBaseValueActivation, BaseTypeActivationHandler: e.getBaseTypeActivation, ValidTopLevelDeclarationsHandler: validTopLevelDeclarations, - LocationHandler: e.newLocationHandler(), + LocationHandler: e.ResolveLocation, ImportHandler: e.resolveImport, CheckHandler: e.newCheckHandler(), AttachmentsEnabled: e.config.AttachmentsEnabled, @@ -644,16 +646,20 @@ func (e *interpreterEnvironment) check( return elaboration, nil } -func (e *interpreterEnvironment) newLocationHandler() sema.LocationHandlerFunc { - return func(identifiers []Identifier, location Location) (res []ResolvedLocation, err error) { - errors.WrapPanic(func() { - res, err = e.runtimeInterface.ResolveLocation(identifiers, location) - }) - if err != nil { - err = interpreter.WrappedExternalError(err) - } - return +func (e *interpreterEnvironment) ResolveLocation( + identifiers []Identifier, + location Location, +) ( + res []ResolvedLocation, + err error, +) { + errors.WrapPanic(func() { + res, err = e.runtimeInterface.ResolveLocation(identifiers, location) + }) + if err != nil { + err = interpreter.WrappedExternalError(err) } + return } func (e *interpreterEnvironment) newCheckHandler() sema.CheckHandlerFunc { diff --git a/runtime/runtime.go b/runtime/runtime.go index 1bb4254d84..e385f2c0c7 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -357,6 +357,7 @@ func UserPanicToError(f func()) (returnedError error) { type ArgumentDecoder interface { stdlib.StandardLibraryHandler + ResolveLocation(identifiers []ast.Identifier, location common.Location) ([]ResolvedLocation, error) // DecodeArgument decodes a transaction/script argument against the given type. DecodeArgument(argument []byte, argumentType cadence.Type) (cadence.Value, error) @@ -414,6 +415,7 @@ func validateArgumentParams( inter, locationRange, decoder, + decoder.ResolveLocation, value, parameterType, ) diff --git a/tests/convertValues_test.go b/tests/convertValues_test.go index bbe37f6b3d..1ad217ad69 100644 --- a/tests/convertValues_test.go +++ b/tests/convertValues_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/cadence" + "github.com/onflow/cadence/ast" "github.com/onflow/cadence/common" "github.com/onflow/cadence/encoding/json" "github.com/onflow/cadence/errors" @@ -628,6 +629,7 @@ func TestRuntimeImportValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, tt.value, tt.expectedType, ) @@ -1367,6 +1369,7 @@ func TestImportInclusiveRangeValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, value, sema.NewInclusiveRangeType(inter, sema.IntType), ) @@ -1401,6 +1404,7 @@ func TestImportInclusiveRangeValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, value, sema.AnyStructType, ) @@ -1435,6 +1439,7 @@ func TestImportInclusiveRangeValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, value, sema.AnyStructType, ) @@ -1465,6 +1470,7 @@ func TestImportInclusiveRangeValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, value, sema.StringType, ) @@ -3489,6 +3495,7 @@ func TestRuntimeImportExportArrayValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, value, sema.ByteArrayType, ) @@ -3559,6 +3566,7 @@ func TestRuntimeImportExportArrayValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, value, &sema.VariableSizedType{ Type: sema.AnyStructType, @@ -3604,6 +3612,7 @@ func TestRuntimeImportExportArrayValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, value, sema.AnyStructType, ) @@ -3693,6 +3702,7 @@ func TestRuntimeImportExportDictionaryValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, value, &sema.DictionaryType{ KeyType: sema.StringType, @@ -3779,6 +3789,7 @@ func TestRuntimeImportExportDictionaryValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, value, &sema.DictionaryType{ KeyType: sema.StringType, @@ -3843,6 +3854,7 @@ func TestRuntimeImportExportDictionaryValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, value, sema.AnyStructType, ) @@ -3910,6 +3922,7 @@ func TestRuntimeImportExportDictionaryValue(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, dictionaryWithHeterogenousKeys, sema.AnyStructType, ) @@ -5023,6 +5036,7 @@ func TestRuntimeImportExportComplex(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, externalCompositeValue, semaCompositeType, ) @@ -5552,3 +5566,96 @@ func TestRuntimeExportInterfaceType(t *testing.T) { require.ErrorAs(t, err, &invalidReturnType) }) } +func TestRuntimeImportResolvedLocation(t *testing.T) { + + t.Parallel() + + addressLocation := common.AddressLocation{ + Address: common.MustBytesToAddress([]byte{42}), + Name: "Test", + } + + identifierLocation := common.IdentifierLocation("Test") + + storage := NewUnmeteredInMemoryStorage() + + program := &interpreter.Program{ + Elaboration: sema.NewElaboration(nil), + } + + inter, err := interpreter.NewInterpreter( + program, + addressLocation, + &interpreter.Config{ + Storage: storage, + AtreeValueValidationEnabled: true, + AtreeStorageValidationEnabled: true, + }, + ) + require.NoError(t, err) + + semaCompositeType := &sema.CompositeType{ + Location: addressLocation, + Identifier: "Foo", + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + } + + program.Elaboration.SetCompositeType( + semaCompositeType.ID(), + semaCompositeType, + ) + + externalCompositeType := cadence.NewStructType( + identifierLocation, + "Foo", + []cadence.Field{}, + nil, + ) + + externalCompositeValue := cadence.NewStruct(nil). + WithType(externalCompositeType) + + resolveLocation := func( + identifiers []ast.Identifier, + location common.Location, + ) ([]sema.ResolvedLocation, error) { + require.Equal(t, identifierLocation, location) + + location = addressLocation + + return []sema.ResolvedLocation{ + { + Location: location, + Identifiers: identifiers, + }, + }, nil + } + + actual, err := ImportValue( + inter, + interpreter.EmptyLocationRange, + nil, + resolveLocation, + externalCompositeValue, + semaCompositeType, + ) + require.NoError(t, err) + + internalCompositeValue := interpreter.NewCompositeValue( + inter, + interpreter.EmptyLocationRange, + addressLocation, + "Foo", + common.CompositeKindStructure, + nil, + common.ZeroAddress, + ) + + AssertValuesEqual( + t, + inter, + internalCompositeValue, + actual, + ) +} diff --git a/tests/deployment_test.go b/tests/deployment_test.go index daa8845812..475cceb86f 100644 --- a/tests/deployment_test.go +++ b/tests/deployment_test.go @@ -71,6 +71,7 @@ func TestRuntimeTransactionWithContractDeployment(t *testing.T) { inter, interpreter.EmptyLocationRange, nil, + nil, codeHashValue, stdlib.HashType, ) diff --git a/tests/runtime_utils/location.go b/tests/runtime_utils/location.go index 8ae9b3614e..f9528eb4f6 100644 --- a/tests/runtime_utils/location.go +++ b/tests/runtime_utils/location.go @@ -49,13 +49,7 @@ func NewScriptLocationGenerator() func() common.ScriptLocation { return NewLocationGenerator[common.ScriptLocation]() } -func NewSingleIdentifierLocationResolver(t testing.TB) func( - identifiers []runtime.Identifier, - location runtime.Location, -) ( - []runtime.ResolvedLocation, - error, -) { +func NewSingleIdentifierLocationResolver(t testing.TB) sema.LocationHandlerFunc { return func( identifiers []runtime.Identifier, location runtime.Location, diff --git a/tests/runtime_utils/testinterface.go b/tests/runtime_utils/testinterface.go index afb24d97ca..9b3b8199e3 100644 --- a/tests/runtime_utils/testinterface.go +++ b/tests/runtime_utils/testinterface.go @@ -40,13 +40,7 @@ import ( type TestRuntimeInterface struct { Storage TestLedger - OnResolveLocation func( - identifiers []runtime.Identifier, - location runtime.Location, - ) ( - []runtime.ResolvedLocation, - error, - ) + OnResolveLocation sema.LocationHandlerFunc OnGetCode func(_ runtime.Location) ([]byte, error) OnGetAndSetProgram func( location runtime.Location,