From 7cb1074a3d80c553c24b7367583e109bfbca248b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 22 Sep 2023 10:24:02 -0700 Subject: [PATCH 1/2] add support for injecting types into the environment --- runtime/environment.go | 21 +++++++--- runtime/predeclaredvalues_test.go | 65 +++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 9 deletions(-) diff --git a/runtime/environment.go b/runtime/environment.go index 32a5f8b024..91011df111 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -38,7 +38,8 @@ import ( type Environment interface { ArgumentDecoder - Declare(valueDeclaration stdlib.StandardLibraryValue) + DeclareValue(valueDeclaration stdlib.StandardLibraryValue) + DeclareType(typeDeclaration stdlib.StandardLibraryType) Configure( runtimeInterface Interface, codesAndPrograms CodesAndPrograms, @@ -78,8 +79,9 @@ type interpreterEnvironmentReconfigured struct { type interpreterEnvironment struct { interpreterEnvironmentReconfigured - baseActivation *interpreter.VariableActivation + baseTypeActivation *sema.VariableActivation baseValueActivation *sema.VariableActivation + baseActivation *interpreter.VariableActivation InterpreterConfig *interpreter.Config CheckerConfig *sema.Config deployedContractConstructorInvocation *stdlib.DeployedContractConstructorInvocation @@ -108,12 +110,14 @@ var _ common.MemoryGauge = &interpreterEnvironment{} func newInterpreterEnvironment(config Config) *interpreterEnvironment { baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseTypeActivation := sema.NewVariableActivation(sema.BaseTypeActivation) baseActivation := activations.NewActivation[*interpreter.Variable](nil, interpreter.BaseActivation) env := &interpreterEnvironment{ config: config, - baseActivation: baseActivation, baseValueActivation: baseValueActivation, + baseTypeActivation: baseTypeActivation, + baseActivation: baseActivation, stackDepthLimiter: newStackDepthLimiter(config.StackDepthLimit), } env.InterpreterConfig = env.newInterpreterConfig() @@ -158,6 +162,7 @@ func (e *interpreterEnvironment) newCheckerConfig() *sema.Config { return &sema.Config{ AccessCheckMode: sema.AccessCheckModeStrict, BaseValueActivation: e.baseValueActivation, + BaseTypeActivation: e.baseTypeActivation, ValidTopLevelDeclarationsHandler: validTopLevelDeclarations, LocationHandler: e.newLocationHandler(), ImportHandler: e.resolveImport, @@ -171,7 +176,7 @@ func (e *interpreterEnvironment) newCheckerConfig() *sema.Config { func NewBaseInterpreterEnvironment(config Config) *interpreterEnvironment { env := newInterpreterEnvironment(config) for _, valueDeclaration := range stdlib.DefaultStandardLibraryValues(env) { - env.Declare(valueDeclaration) + env.DeclareValue(valueDeclaration) } return env } @@ -179,7 +184,7 @@ func NewBaseInterpreterEnvironment(config Config) *interpreterEnvironment { func NewScriptInterpreterEnvironment(config Config) Environment { env := newInterpreterEnvironment(config) for _, valueDeclaration := range stdlib.DefaultScriptStandardLibraryValues(env) { - env.Declare(valueDeclaration) + env.DeclareValue(valueDeclaration) } return env } @@ -198,11 +203,15 @@ func (e *interpreterEnvironment) Configure( e.stackDepthLimiter.depth = 0 } -func (e *interpreterEnvironment) Declare(valueDeclaration stdlib.StandardLibraryValue) { +func (e *interpreterEnvironment) DeclareValue(valueDeclaration stdlib.StandardLibraryValue) { e.baseValueActivation.DeclareValue(valueDeclaration) interpreter.Declare(e.baseActivation, valueDeclaration) } +func (e *interpreterEnvironment) DeclareType(typeDeclaration stdlib.StandardLibraryType) { + e.baseTypeActivation.DeclareType(typeDeclaration) +} + func (e *interpreterEnvironment) NewAuthAccountValue(address interpreter.AddressValue) interpreter.Value { return stdlib.NewAuthAccountValue(e, e, address) } diff --git a/runtime/predeclaredvalues_test.go b/runtime/predeclaredvalues_test.go index 069838164b..fee5147550 100644 --- a/runtime/predeclaredvalues_test.go +++ b/runtime/predeclaredvalues_test.go @@ -39,7 +39,7 @@ func TestRuntimePredeclaredValues(t *testing.T) { valueDeclaration := stdlib.StandardLibraryValue{ Name: "foo", Type: sema.IntType, - Kind: common.DeclarationKindFunction, + Kind: common.DeclarationKindConstant, Value: interpreter.NewUnmeteredIntValueFromInt64(2), } @@ -91,7 +91,7 @@ func TestRuntimePredeclaredValues(t *testing.T) { // Run transaction transactionEnvironment := NewBaseInterpreterEnvironment(Config{}) - transactionEnvironment.Declare(valueDeclaration) + transactionEnvironment.DeclareValue(valueDeclaration) err := runtime.ExecuteTransaction( Script{ @@ -108,7 +108,7 @@ func TestRuntimePredeclaredValues(t *testing.T) { // Run script scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) - scriptEnvironment.Declare(valueDeclaration) + scriptEnvironment.DeclareValue(valueDeclaration) result, err := runtime.ExecuteScript( Script{ @@ -127,3 +127,62 @@ func TestRuntimePredeclaredValues(t *testing.T) { result, ) } + +func TestRuntimePredeclaredTypes(t *testing.T) { + + t.Parallel() + + xType := sema.IntType + + valueDeclaration := stdlib.StandardLibraryValue{ + Name: "x", + Type: xType, + Kind: common.DeclarationKindConstant, + Value: interpreter.NewUnmeteredIntValueFromInt64(2), + } + + typeDeclaration := stdlib.StandardLibraryType{ + Name: "X", + Type: xType, + Kind: common.DeclarationKindType, + } + + script := []byte(` + pub fun main(): X { + return x + } + `) + + runtime := newTestInterpreterRuntime() + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{common.MustBytesToAddress([]byte{0x1})}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + } + + // Run script + + scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) + scriptEnvironment.DeclareValue(valueDeclaration) + scriptEnvironment.DeclareType(typeDeclaration) + + result, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + Environment: scriptEnvironment, + }, + ) + require.NoError(t, err) + + require.Equal(t, + cadence.Int{Value: big.NewInt(2)}, + result, + ) +} From 957de13ed7bfe480ad5c328cdba6be3e190f7004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 22 Sep 2023 13:35:25 -0700 Subject: [PATCH 2/2] add support for injecting composite types, potentially nested: perform lookup in base type activation --- runtime/environment.go | 15 +- runtime/interpreter/interpreter.go | 18 +- runtime/predeclaredvalues_test.go | 401 +++++++++++++++++++++++++---- runtime/sema/type.go | 34 ++- 4 files changed, 404 insertions(+), 64 deletions(-) diff --git a/runtime/environment.go b/runtime/environment.go index 91011df111..1edd4bcaef 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -897,8 +897,21 @@ func (e *interpreterEnvironment) newImportLocationHandler() interpreter.ImportLo func (e *interpreterEnvironment) newCompositeTypeHandler() interpreter.CompositeTypeHandlerFunc { return func(location common.Location, typeID common.TypeID) *sema.CompositeType { - if _, ok := location.(stdlib.FlowLocation); ok { + + switch location.(type) { + case stdlib.FlowLocation: return stdlib.FlowEventTypes[typeID] + + case nil: + qualifiedIdentifier := string(typeID) + ty := sema.TypeActivationNestedType(e.baseTypeActivation, qualifiedIdentifier) + if ty == nil { + return nil + } + + if compositeType, ok := ty.(*sema.CompositeType); ok { + return compositeType + } } return nil diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index acb6ee8bbf..de338e7b8d 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -4796,16 +4796,18 @@ func (interpreter *Interpreter) GetCompositeType( if compositeType != nil { return compositeType, nil } - } else { - config := interpreter.SharedState.Config - compositeTypeHandler := config.CompositeTypeHandler - if compositeTypeHandler != nil { - compositeType = compositeTypeHandler(location, typeID) - if compositeType != nil { - return compositeType, nil - } + } + + config := interpreter.SharedState.Config + compositeTypeHandler := config.CompositeTypeHandler + if compositeTypeHandler != nil { + compositeType = compositeTypeHandler(location, typeID) + if compositeType != nil { + return compositeType, nil } + } + if location != nil { compositeType = interpreter.getUserCompositeType(location, typeID) if compositeType != nil { return compositeType, nil diff --git a/runtime/predeclaredvalues_test.go b/runtime/predeclaredvalues_test.go index fee5147550..57a86788e4 100644 --- a/runtime/predeclaredvalues_test.go +++ b/runtime/predeclaredvalues_test.go @@ -29,7 +29,7 @@ import ( "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" - "github.com/onflow/cadence/runtime/tests/utils" + . "github.com/onflow/cadence/runtime/tests/utils" ) func TestRuntimePredeclaredValues(t *testing.T) { @@ -61,7 +61,7 @@ func TestRuntimePredeclaredValues(t *testing.T) { runtime := newTestInterpreterRuntime() - deploy := utils.DeploymentTransaction("C", contract) + deploy := DeploymentTransaction("C", contract) var accountCode []byte var events []cadence.Event @@ -132,57 +132,350 @@ func TestRuntimePredeclaredTypes(t *testing.T) { t.Parallel() - xType := sema.IntType + t.Run("type alias", func(t *testing.T) { + t.Parallel() + + xType := sema.IntType + + valueDeclaration := stdlib.StandardLibraryValue{ + Name: "x", + Type: xType, + Kind: common.DeclarationKindConstant, + Value: interpreter.NewUnmeteredIntValueFromInt64(2), + } + + typeDeclaration := stdlib.StandardLibraryType{ + Name: "X", + Type: xType, + Kind: common.DeclarationKindType, + } + + script := []byte(` + pub fun main(): X { + return x + } + `) + + runtime := newTestInterpreterRuntime() + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{common.MustBytesToAddress([]byte{0x1})}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + } + + // Run script + + scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) + scriptEnvironment.DeclareValue(valueDeclaration) + scriptEnvironment.DeclareType(typeDeclaration) + + result, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + Environment: scriptEnvironment, + }, + ) + require.NoError(t, err) + + require.Equal(t, + cadence.Int{Value: big.NewInt(2)}, + result, + ) + }) + + t.Run("composite type, top-level, existing", func(t *testing.T) { + t.Parallel() + + xType := &sema.CompositeType{ + Identifier: "X", + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + } + + valueDeclaration := stdlib.StandardLibraryValue{ + Name: "x", + Type: xType, + Kind: common.DeclarationKindConstant, + Value: interpreter.NewSimpleCompositeValue(nil, + xType.ID(), + interpreter.ConvertSemaCompositeTypeToStaticCompositeType(nil, xType), + nil, + nil, + nil, + nil, + nil, + ), + } + + typeDeclaration := stdlib.StandardLibraryType{ + Name: "X", + Type: xType, + Kind: common.DeclarationKindType, + } + + script := []byte(` + pub fun main(): X { + return x + } + `) + + runtime := newTestInterpreterRuntime() + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{common.MustBytesToAddress([]byte{0x1})}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + } + + // Run script + + scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) + scriptEnvironment.DeclareValue(valueDeclaration) + scriptEnvironment.DeclareType(typeDeclaration) + + result, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + Environment: scriptEnvironment, + }, + ) + require.NoError(t, err) + + require.Equal(t, + cadence.ValueWithCachedTypeID( + cadence.Struct{ + StructType: cadence.NewStructType(nil, xType.QualifiedIdentifier(), []cadence.Field{}, nil), + Fields: []cadence.Value{}, + }, + ), + result, + ) + }) + + t.Run("composite type, top-level, non-existing", func(t *testing.T) { + t.Parallel() + + xType := &sema.CompositeType{ + Identifier: "X", + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + } + + valueDeclaration := stdlib.StandardLibraryValue{ + Name: "x", + Type: xType, + Kind: common.DeclarationKindConstant, + Value: interpreter.NewSimpleCompositeValue(nil, + xType.ID(), + interpreter.ConvertSemaCompositeTypeToStaticCompositeType(nil, xType), + nil, + nil, + nil, + nil, + nil, + ), + } + + script := []byte(` + pub fun main(): AnyStruct { + return x + } + `) + + runtime := newTestInterpreterRuntime() + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{common.MustBytesToAddress([]byte{0x1})}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + } + + // Run script + + scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) + scriptEnvironment.DeclareValue(valueDeclaration) + + _, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + Environment: scriptEnvironment, + }, + ) + RequireError(t, err) + + var typeLoadingErr interpreter.TypeLoadingError + require.ErrorAs(t, err, &typeLoadingErr) + }) + + t.Run("composite type, nested, existing", func(t *testing.T) { + t.Parallel() + + yType := &sema.CompositeType{ + Identifier: "Y", + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + } + + xType := &sema.CompositeType{ + Identifier: "X", + Kind: common.CompositeKindContract, + Members: &sema.StringMemberOrderedMap{}, + } + + xType.SetNestedType(yType.Identifier, yType) + + valueDeclaration := stdlib.StandardLibraryValue{ + Name: "y", + Type: yType, + Kind: common.DeclarationKindConstant, + Value: interpreter.NewSimpleCompositeValue(nil, + yType.ID(), + interpreter.ConvertSemaCompositeTypeToStaticCompositeType(nil, yType), + nil, + nil, + nil, + nil, + nil, + ), + } + + typeDeclaration := stdlib.StandardLibraryType{ + Name: "X", + Type: xType, + Kind: common.DeclarationKindType, + } + + script := []byte(` + pub fun main(): X.Y { + return y + } + `) + + runtime := newTestInterpreterRuntime() + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{common.MustBytesToAddress([]byte{0x1})}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + } + + // Run script + + scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) + scriptEnvironment.DeclareValue(valueDeclaration) + scriptEnvironment.DeclareType(typeDeclaration) + + result, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + Environment: scriptEnvironment, + }, + ) + require.NoError(t, err) + + require.Equal(t, + cadence.ValueWithCachedTypeID( + cadence.Struct{ + StructType: cadence.NewStructType(nil, yType.QualifiedIdentifier(), []cadence.Field{}, nil), + Fields: []cadence.Value{}, + }, + ), + result, + ) + }) + + t.Run("composite type, nested, non-existing", func(t *testing.T) { + t.Parallel() + + yType := &sema.CompositeType{ + Identifier: "Y", + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + } + + xType := &sema.CompositeType{ + Identifier: "X", + Kind: common.CompositeKindContract, + Members: &sema.StringMemberOrderedMap{}, + } + + xType.SetNestedType(yType.Identifier, yType) + + valueDeclaration := stdlib.StandardLibraryValue{ + Name: "y", + Type: yType, + Kind: common.DeclarationKindConstant, + Value: interpreter.NewSimpleCompositeValue(nil, + yType.ID(), + interpreter.ConvertSemaCompositeTypeToStaticCompositeType(nil, yType), + nil, + nil, + nil, + nil, + nil, + ), + } + + script := []byte(` + pub fun main(): AnyStruct { + return y + } + `) + + runtime := newTestInterpreterRuntime() + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{common.MustBytesToAddress([]byte{0x1})}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + } + + // Run script + + scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) + scriptEnvironment.DeclareValue(valueDeclaration) + + _, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + Environment: scriptEnvironment, + }, + ) + RequireError(t, err) + + var typeLoadingErr interpreter.TypeLoadingError + require.ErrorAs(t, err, &typeLoadingErr) + }) - valueDeclaration := stdlib.StandardLibraryValue{ - Name: "x", - Type: xType, - Kind: common.DeclarationKindConstant, - Value: interpreter.NewUnmeteredIntValueFromInt64(2), - } - - typeDeclaration := stdlib.StandardLibraryType{ - Name: "X", - Type: xType, - Kind: common.DeclarationKindType, - } - - script := []byte(` - pub fun main(): X { - return x - } - `) - - runtime := newTestInterpreterRuntime() - - runtimeInterface := &testRuntimeInterface{ - storage: newTestLedger(nil, nil), - getSigningAccounts: func() ([]Address, error) { - return []Address{common.MustBytesToAddress([]byte{0x1})}, nil - }, - resolveLocation: singleIdentifierLocationResolver(t), - } - - // Run script - - scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) - scriptEnvironment.DeclareValue(valueDeclaration) - scriptEnvironment.DeclareType(typeDeclaration) - - result, err := runtime.ExecuteScript( - Script{ - Source: script, - }, - Context{ - Interface: runtimeInterface, - Location: common.ScriptLocation{}, - Environment: scriptEnvironment, - }, - ) - require.NoError(t, err) - - require.Equal(t, - cadence.Int{Value: big.NewInt(2)}, - result, - ) } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 9e2ab8b422..4eb524bc5e 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -31,6 +31,8 @@ import ( "github.com/onflow/cadence/runtime/errors" ) +const TypeIDSeparator = '.' + func qualifiedIdentifier(identifier string, containerType Type) string { if containerType == nil { return identifier @@ -56,7 +58,7 @@ func qualifiedIdentifier(identifier string, containerType Type) string { for i := len(identifiers) - 1; i >= 0; i-- { sb.WriteString(identifiers[i]) if i != 0 { - sb.WriteByte('.') + sb.WriteByte(TypeIDSeparator) } } @@ -229,6 +231,36 @@ func VisitThisAndNested(t Type, visit func(ty Type)) { }) } +func TypeActivationNestedType(typeActivation *VariableActivation, qualifiedIdentifier string) Type { + + typeIDComponents := strings.Split(qualifiedIdentifier, string(TypeIDSeparator)) + + rootTypeName := typeIDComponents[0] + variable := typeActivation.Find(rootTypeName) + if variable == nil { + return nil + } + ty := variable.Type + + // Traverse nested types until the leaf type + + for i := 1; i < len(typeIDComponents); i++ { + containerType, ok := ty.(ContainerType) + if !ok || !containerType.IsContainerType() { + return nil + } + + typeIDComponent := typeIDComponents[i] + + ty, ok = containerType.GetNestedTypes().Get(typeIDComponent) + if !ok { + return nil + } + } + + return ty +} + // CompositeKindedType is a type which has a composite kind type CompositeKindedType interface { Type