diff --git a/migrations/capcons/capabilitymigration.go b/migrations/capcons/capabilitymigration.go index a37fd234e7..3aae29ff45 100644 --- a/migrations/capcons/capabilitymigration.go +++ b/migrations/capcons/capabilitymigration.go @@ -86,9 +86,11 @@ func (m *CapabilityValueMigration) Migrate( break } - newBorrowType, ok := oldCapability.BorrowType.(*interpreter.ReferenceStaticType) + oldBorrowType := oldCapability.BorrowType + + newBorrowType, ok := oldBorrowType.(*interpreter.ReferenceStaticType) if !ok { - panic(errors.NewUnreachableError()) + panic(errors.NewUnexpectedError("unexpected non-reference borrow type: %T", oldBorrowType)) } // Convert the old AuthAccount type to the new fully-entitled Account type diff --git a/migrations/capcons/linkmigration.go b/migrations/capcons/linkmigration.go index 49fed2dbaa..02f385ccbb 100644 --- a/migrations/capcons/linkmigration.go +++ b/migrations/capcons/linkmigration.go @@ -93,9 +93,10 @@ func (m *LinkValueMigration) Migrate( case interpreter.PathLinkValue: //nolint:staticcheck var ok bool - borrowStaticType, ok = readValue.Type.(*interpreter.ReferenceStaticType) + borrowType := readValue.Type + borrowStaticType, ok = borrowType.(*interpreter.ReferenceStaticType) if !ok { - panic(errors.NewUnreachableError()) + panic(errors.NewUnexpectedError("unexpected non-reference borrow type: %T", borrowType)) } case interpreter.AccountLinkValue: //nolint:staticcheck @@ -106,12 +107,13 @@ func (m *LinkValueMigration) Migrate( ) default: - panic(errors.NewUnreachableError()) + panic(errors.NewUnexpectedError("unexpected value type: %T", value)) } - borrowType, ok := inter.MustConvertStaticToSemaType(borrowStaticType).(*sema.ReferenceType) + convertedBorrowStaticType := inter.MustConvertStaticToSemaType(borrowStaticType) + borrowType, ok := convertedBorrowStaticType.(*sema.ReferenceType) if !ok { - panic(errors.NewUnreachableError()) + panic(errors.NewUnexpectedError("unexpected non-reference borrow type: %T", borrowType)) } // Get target @@ -176,7 +178,7 @@ func (m *LinkValueMigration) Migrate( ) default: - panic(errors.NewUnreachableError()) + panic(errors.NewUnexpectedError("unexpected target type: %T", target)) } // Record new capability ID in source path mapping. @@ -310,9 +312,13 @@ func (m *LinkValueMigration) getPathCapabilityFinalTarget( // so it's possible that a capability value is encountered when determining the final target, // when a part of the full link chain was already previously migrated. - capabilityBorrowType, ok := inter.MustConvertStaticToSemaType(value.BorrowType).(*sema.ReferenceType) + convertedBorrowType := inter.MustConvertStaticToSemaType(value.BorrowType) + capabilityBorrowType, ok := convertedBorrowType.(*sema.ReferenceType) if !ok { - panic(errors.NewUnreachableError()) + panic(errors.NewUnexpectedError( + "unexpected non-reference borrow type: %T", + convertedBorrowType, + )) } // Do not borrow final target (i.e. do not require target to exist), @@ -349,7 +355,7 @@ func (m *LinkValueMigration) getPathCapabilityFinalTarget( } default: - panic(errors.NewUnreachableError()) + panic(errors.NewUnexpectedError("unexpected value type: %T", value)) } } } diff --git a/migrations/entitlements/migration_test.go b/migrations/entitlements/migration_test.go index acba435b72..7ad61a4155 100644 --- a/migrations/entitlements/migration_test.go +++ b/migrations/entitlements/migration_test.go @@ -909,6 +909,11 @@ func TestConvertToEntitledValue(t *testing.T) { ), Name: "[Type]", }, + { + Input: interpreter.NewTypeValue(inter, nil), + Output: interpreter.NewTypeValue(inter, nil), + Name: "Type(nil)", + }, { Input: interpreter.NewDictionaryValue( inter, @@ -1041,6 +1046,19 @@ func TestConvertToEntitledValue(t *testing.T) { }, Name: "PathCapability<&S>", }, + { + Input: &interpreter.PathCapabilityValue{ //nolint:staticcheck + Address: interpreter.NewAddressValue(inter, testAddress), + Path: testPathValue, + BorrowType: nil, + }, + Output: &interpreter.PathCapabilityValue{ //nolint:staticcheck + Address: interpreter.NewAddressValue(inter, testAddress), + Path: testPathValue, + BorrowType: nil, + }, + Name: "PathCapability", + }, { Input: interpreter.NewCapabilityValue( inter, @@ -1276,6 +1294,21 @@ func TestConvertToEntitledValue(t *testing.T) { } return true + case interpreter.TypeValue: + // TypeValue considers missing type "unknown"/"invalid", + // and "unknown"/"invalid" type values unequal. + // However, we want to consider those equal here for testing/asserting purposes + other, ok := output.(interpreter.TypeValue) + if !ok { + return false + } + + if other.Type == nil { + return v.Type == nil + } else { + return other.Type.Equal(v.Type) + } + case *interpreter.EphemeralReferenceValue: otherReference, ok := output.(*interpreter.EphemeralReferenceValue) if !ok || !v.Authorization.Equal(otherReference.Authorization) { @@ -1304,7 +1337,7 @@ func TestConvertToEntitledValue(t *testing.T) { convertedValue := convertEntireTestValue(inter, storage, testAddress, test.Input) switch convertedValue := convertedValue.(type) { case interpreter.EquatableValue: - require.True(t, referencePeekingEqual(convertedValue, test.Output)) + require.True(t, referencePeekingEqual(convertedValue, test.Output), "expected: %s\nactual: %s", test.Output, convertedValue) default: require.Equal(t, convertedValue, test.Output) } diff --git a/migrations/legacy_primitivestatic_type.go b/migrations/legacy_primitivestatic_type.go index c44b26f592..172563fe53 100644 --- a/migrations/legacy_primitivestatic_type.go +++ b/migrations/legacy_primitivestatic_type.go @@ -33,7 +33,9 @@ type LegacyPrimitiveStaticType struct { var _ interpreter.StaticType = LegacyPrimitiveStaticType{} func (t LegacyPrimitiveStaticType) ID() common.TypeID { - switch t.PrimitiveStaticType { + primitiveStaticType := t.PrimitiveStaticType + + switch primitiveStaticType { case interpreter.PrimitiveStaticTypeAuthAccount: //nolint:staticcheck return "AuthAccount" case interpreter.PrimitiveStaticTypePublicAccount: //nolint:staticcheck @@ -59,7 +61,6 @@ func (t LegacyPrimitiveStaticType) ID() common.TypeID { case interpreter.PrimitiveStaticTypeAccountKey: //nolint:staticcheck return "AccountKey" default: - panic(errors.NewUnreachableError()) - + panic(errors.NewUnexpectedError("unexpected non-legacy primitive static type: %s", primitiveStaticType)) } } diff --git a/migrations/migration.go b/migrations/migration.go index a833200bf1..84522e7496 100644 --- a/migrations/migration.go +++ b/migrations/migration.go @@ -306,7 +306,10 @@ func (m *StorageMigration) MigrateNestedValue( ) if _, ok := oldValue.(*interpreter.SomeValue); !ok { - panic(errors.NewUnreachableError()) + panic(errors.NewUnexpectedError( + "failed to remove old value for migrated key: %s", + existingKey, + )) } keyToSet = newKey @@ -372,8 +375,14 @@ func (m *StorageMigration) MigrateNestedValue( // is the same as the owner of the old value if ownedValue, ok := value.(interpreter.OwnedValue); ok { if ownedConvertedValue, ok := convertedValue.(interpreter.OwnedValue); ok { - if ownedConvertedValue.GetOwner() != ownedValue.GetOwner() { - panic(errors.NewUnreachableError()) + convertedOwner := ownedConvertedValue.GetOwner() + originalOwner := ownedValue.GetOwner() + if convertedOwner != originalOwner { + panic(errors.NewUnexpectedError( + "migrated value has different owner: expected %s, got %s", + originalOwner, + convertedOwner, + )) } } } diff --git a/migrations/statictypes/account_type_migration_test.go b/migrations/statictypes/account_type_migration_test.go index 7441da769b..e7bf93e620 100644 --- a/migrations/statictypes/account_type_migration_test.go +++ b/migrations/statictypes/account_type_migration_test.go @@ -42,6 +42,7 @@ type testReporter struct { interpreter.StorageKey interpreter.StorageMapKey }]struct{} + errors []error } func newTestReporter() *testReporter { @@ -72,8 +73,9 @@ func (t *testReporter) Error( _ interpreter.StorageKey, _ interpreter.StorageMapKey, _ string, - _ error, + err error, ) { + t.errors = append(t.errors, err) } func TestAccountTypeInTypeValueMigration(t *testing.T) { @@ -424,6 +426,8 @@ func TestAccountTypeInTypeValueMigration(t *testing.T) { err = migration.Commit() require.NoError(t, err) + require.Empty(t, reporter.errors) + // Check reported migrated paths for identifier, test := range testCases { key := struct { @@ -1080,6 +1084,8 @@ func TestAccountTypeRehash(t *testing.T) { err := migration.Commit() require.NoError(t, err) + require.Empty(t, reporter.errors) + require.Equal(t, map[struct { interpreter.StorageKey diff --git a/migrations/statictypes/composite_type_migration_test.go b/migrations/statictypes/composite_type_migration_test.go index 6df0ed7b69..87f15b2dbc 100644 --- a/migrations/statictypes/composite_type_migration_test.go +++ b/migrations/statictypes/composite_type_migration_test.go @@ -172,6 +172,8 @@ func TestCompositeAndInterfaceTypeMigration(t *testing.T) { err = migration.Commit() require.NoError(t, err) + require.Empty(t, reporter.errors) + // Check reported migrated paths for identifier, test := range testCases { key := struct { diff --git a/migrations/statictypes/intersection_type_migration_test.go b/migrations/statictypes/intersection_type_migration_test.go index eec47b8792..b0831df61e 100644 --- a/migrations/statictypes/intersection_type_migration_test.go +++ b/migrations/statictypes/intersection_type_migration_test.go @@ -443,6 +443,8 @@ func TestIntersectionTypeMigration(t *testing.T) { err = migration.Commit() require.NoError(t, err) + require.Empty(t, reporter.errors) + // Check reported migrated paths for identifier, test := range testCases { key := struct { @@ -611,6 +613,8 @@ func TestIntersectionTypeRehash(t *testing.T) { err := migration.Commit() require.NoError(t, err) + require.Empty(t, reporter.errors) + require.Equal(t, map[struct { interpreter.StorageKey @@ -775,6 +779,8 @@ func TestRehashNestedIntersectionType(t *testing.T) { err := migration.Commit() require.NoError(t, err) + require.Empty(t, reporter.errors) + require.Equal(t, map[struct { interpreter.StorageKey @@ -914,6 +920,8 @@ func TestRehashNestedIntersectionType(t *testing.T) { err := migration.Commit() require.NoError(t, err) + require.Empty(t, reporter.errors) + require.Equal(t, map[struct { interpreter.StorageKey @@ -1114,6 +1122,8 @@ func TestIntersectionTypeMigrationWithInterfaceTypeConverter(t *testing.T) { err = migration.Commit() require.NoError(t, err) + require.Empty(t, reporter.errors) + expectLegacyTypeConverted := convertCompositeType && legacyType != nil expectInterfaceTypeConverted := convertInterfaceType && len(interfaceTypeQualifiedIdentifiers) > 0 expectMigration := len(interfaceTypeQualifiedIdentifiers) >= 2 || @@ -1282,6 +1292,8 @@ func TestIntersectionTypeMigrationWithTypeConverters(t *testing.T) { err = migration.Commit() require.NoError(t, err) + require.Empty(t, reporter.errors) + key := struct { interpreter.StorageKey interpreter.StorageMapKey diff --git a/migrations/statictypes/statictype_migration.go b/migrations/statictypes/statictype_migration.go index aa006b44be..9daa83c02a 100644 --- a/migrations/statictypes/statictype_migration.go +++ b/migrations/statictypes/statictype_migration.go @@ -66,7 +66,12 @@ func (m *StaticTypeMigration) Migrate( ) (newValue interpreter.Value, err error) { switch value := value.(type) { case interpreter.TypeValue: - convertedType := m.maybeConvertStaticType(value.Type, nil) + // Type is optional. nil represents "unknown"/"invalid" type + ty := value.Type + if ty == nil { + return + } + convertedType := m.maybeConvertStaticType(ty, nil) if convertedType == nil { return } @@ -80,7 +85,12 @@ func (m *StaticTypeMigration) Migrate( return interpreter.NewUnmeteredCapabilityValue(value.ID, value.Address, convertedBorrowType), nil case *interpreter.PathCapabilityValue: //nolint:staticcheck - convertedBorrowType := m.maybeConvertStaticType(value.BorrowType, nil) + // Type is optional + borrowType := value.BorrowType + if borrowType == nil { + return + } + convertedBorrowType := m.maybeConvertStaticType(borrowType, nil) if convertedBorrowType == nil { return } @@ -345,7 +355,7 @@ func (m *StaticTypeMigration) maybeConvertStaticType(staticType, parentType inte } default: - panic(errors.NewUnreachableError()) + panic(errors.NewUnexpectedError("unexpected static type: %T", staticType)) } return nil diff --git a/migrations/statictypes/statictype_migration_test.go b/migrations/statictypes/statictype_migration_test.go new file mode 100644 index 0000000000..84c645f350 --- /dev/null +++ b/migrations/statictypes/statictype_migration_test.go @@ -0,0 +1,152 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package statictypes + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/migrations" + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + . "github.com/onflow/cadence/runtime/tests/runtime_utils" + "github.com/onflow/cadence/runtime/tests/utils" +) + +func TestStaticTypeMigration(t *testing.T) { + t.Parallel() + + migrate := func( + t *testing.T, + staticTypeMigration *StaticTypeMigration, + value interpreter.Value, + ) interpreter.Value { + + // Store values + + ledger := NewTestLedger(nil, nil) + storage := runtime.NewStorage(ledger, nil) + + inter, err := interpreter.NewInterpreter( + nil, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + AtreeValueValidationEnabled: false, + AtreeStorageValidationEnabled: true, + }, + ) + require.NoError(t, err) + + storageMapKey := interpreter.StringStorageMapKey("test_type_value") + storageDomain := common.PathDomainStorage.Identifier() + + inter.WriteStored( + testAddress, + storageDomain, + storageMapKey, + value, + ) + + err = storage.Commit(inter, true) + require.NoError(t, err) + + // Migrate + + migration := migrations.NewStorageMigration(inter, storage) + + reporter := newTestReporter() + + migration.Migrate( + &migrations.AddressSliceIterator{ + Addresses: []common.Address{ + testAddress, + }, + }, + migration.NewValueMigrationsPathMigrator( + reporter, + staticTypeMigration, + ), + ) + + err = migration.Commit() + require.NoError(t, err) + + require.Empty(t, reporter.errors) + + storageMap := storage.GetStorageMap( + testAddress, + storageDomain, + false, + ) + require.NotNil(t, storageMap) + require.Equal(t, uint64(1), storageMap.Count()) + + result := storageMap.ReadValue(nil, storageMapKey) + require.NotNil(t, value) + + return result + } + + t.Run("TypeValue with nil type", func(t *testing.T) { + t.Parallel() + + staticTypeMigration := NewStaticTypeMigration() + + actual := migrate(t, + staticTypeMigration, + interpreter.NewTypeValue(nil, nil), + ) + assert.Equal(t, + interpreter.NewTypeValue(nil, nil), + actual, + ) + }) + + t.Run("PathCapabilityValue with nil borrow type", func(t *testing.T) { + t.Parallel() + + staticTypeMigration := NewStaticTypeMigration() + + path := interpreter.NewUnmeteredPathValue( + common.PathDomainStorage, + "test", + ) + + actual := migrate(t, + staticTypeMigration, + &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: nil, + Path: path, + Address: interpreter.AddressValue(testAddress), + }, + ) + assert.Equal(t, + &interpreter.PathCapabilityValue{ //nolint:staticcheck + BorrowType: nil, + Path: path, + Address: interpreter.AddressValue(testAddress), + }, + actual, + ) + }) +}