From a7c7e91a674d95e89e833c32c53bd05ff2f85174 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 14 Feb 2024 21:50:29 +0530 Subject: [PATCH 1/4] Refactor --- ..._v0.42_to_v1_contract_upgrade_validator.go | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go index b1beda402..8c0bbe909 100644 --- a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go +++ b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go @@ -101,7 +101,10 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) report(err error) { validator.underlyingUpdateValidator.report(err) } -func (validator *CadenceV042ToV1ContractUpdateValidator) idAndLocationOfQualifiedType(typ *ast.NominalType) (common.TypeID, common.Location) { +func (validator *CadenceV042ToV1ContractUpdateValidator) idAndLocationOfQualifiedType(typ *ast.NominalType) ( + common.TypeID, + common.Location, +) { qualifiedString := typ.String() @@ -131,12 +134,16 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) idAndLocationOfQualifie return common.NewTypeIDFromQualifiedName(nil, location, qualifiedString), location } -func (validator *CadenceV042ToV1ContractUpdateValidator) getEntitlementType(entitlement *ast.NominalType) *sema.EntitlementType { +func (validator *CadenceV042ToV1ContractUpdateValidator) getEntitlementType( + entitlement *ast.NominalType, +) *sema.EntitlementType { typeID, location := validator.idAndLocationOfQualifiedType(entitlement) return validator.newElaborations[location].EntitlementType(typeID) } -func (validator *CadenceV042ToV1ContractUpdateValidator) getEntitlementSetAccess(entitlementSet ast.EntitlementSet) sema.EntitlementSetAccess { +func (validator *CadenceV042ToV1ContractUpdateValidator) getEntitlementSetAccess( + entitlementSet ast.EntitlementSet, +) sema.EntitlementSetAccess { var entitlements []*sema.EntitlementType for _, entitlement := range entitlementSet.Entitlements() { @@ -161,9 +168,11 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) getInterfaceType(intf * return validator.newElaborations[location].InterfaceType(typeID) } -func (validator *CadenceV042ToV1ContractUpdateValidator) getIntersectedInterfaces(intersection []*ast.NominalType) (intfs []*sema.InterfaceType) { - for _, intf := range intersection { - intfs = append(intfs, validator.getInterfaceType(intf)) +func (validator *CadenceV042ToV1ContractUpdateValidator) getIntersectedInterfaces( + intersection []*ast.NominalType, +) (interfaceTypes []*sema.InterfaceType) { + for _, interfaceType := range intersection { + interfaceTypes = append(interfaceTypes, validator.getInterfaceType(interfaceType)) } return } @@ -201,7 +210,9 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) expectedAuthorizationOf return sema.NewAccessFromEntitlementSet(supportedEntitlements, sema.Conjunction) } -func (validator *CadenceV042ToV1ContractUpdateValidator) expectedAuthorizationOfIntersection(intersectionTypes []*ast.NominalType) sema.Access { +func (validator *CadenceV042ToV1ContractUpdateValidator) expectedAuthorizationOfIntersection( + intersectionTypes []*ast.NominalType, +) sema.Access { // a reference to an intersection (or restricted) type is granted entitlements based on the intersected interfaces, // ignoring the legacy restricted type, as an intersection type appearing in the new contract means it must have originally From d00bb4dde5c41c288a82f08eba16e42f3dbb0c93 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 15 Feb 2024 19:49:26 +0530 Subject: [PATCH 2/4] Add type requirement removal --- runtime/contract_update_validation_test.go | 21 +++ ..._to_v1_contract_upgrade_validation_test.go | 129 ++++++++++++++++++ ..._v0.42_to_v1_contract_upgrade_validator.go | 59 ++++++++ runtime/stdlib/contract_update_validation.go | 43 ++++-- 4 files changed, 241 insertions(+), 11 deletions(-) diff --git a/runtime/contract_update_validation_test.go b/runtime/contract_update_validation_test.go index 5834a8d76..7b9186b28 100644 --- a/runtime/contract_update_validation_test.go +++ b/runtime/contract_update_validation_test.go @@ -2319,6 +2319,27 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { assertMissingDeclarationError(t, childErrors[1], "B") } }) + + t.Run("Remove event", func(t *testing.T) { + + t.Parallel() + + const oldCode = ` + access(all) contract Test { + access(all) event Foo() + access(all) event Bar() + } + ` + + const newCode = ` + access(all) contract Test { + access(all) event Bar() + } + ` + + err := testDeployAndUpdate(t, "Test", oldCode, newCode) + require.NoError(t, err) + }) } func assertContractRemovalError(t *testing.T, err error, name string) { diff --git a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go index e4968d9b8..0019a6789 100644 --- a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go +++ b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go @@ -817,6 +817,38 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { assertFieldTypeMismatchError(t, cause, "Test", "a", "R", "{I}") }) + t.Run("change field type AnyResource restricted type", func(t *testing.T) { + + t.Parallel() + + const oldCode = ` + pub contract Test { + pub resource interface I {} + pub resource R:I {} + + pub var a: @AnyResource{I} + init() { + self.a <- create R() + } + } + ` + + const newCode = ` + access(all) contract Test { + access(all) resource interface I {} + access(all) resource R:I {} + + access(all) var a: @{I} + init() { + self.a <- create R() + } + } + ` + + err := testContractUpdate(t, oldCode, newCode) + require.NoError(t, err) + }) + t.Run("change field type restricted type variable sized", func(t *testing.T) { t.Parallel() @@ -1263,3 +1295,100 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { assertFieldAuthorizationMismatchError(t, cause, "Test", "a", "E", "E, F") }) } + +func TestTypeRequirementRemoval(t *testing.T) { + + t.Parallel() + + t.Run("resource valid", func(t *testing.T) { + + t.Parallel() + + const oldCode = ` + access(all) contract interface Test { + access(all) resource R {} + access(all) fun foo(r: @R) + } + ` + + const newCode = ` + access(all) contract interface Test { + access(all) resource interface R {} + access(all) fun foo(r: @{R}) + } + ` + + err := testContractUpdate(t, oldCode, newCode) + require.NoError(t, err) + }) + + t.Run("resource invalid", func(t *testing.T) { + + t.Parallel() + + const oldCode = ` + access(all) contract interface Test { + access(all) resource R {} + access(all) fun foo(r: @R) + } + ` + + const newCode = ` + access(all) contract interface Test { + access(all) struct interface R {} + access(all) fun foo(r: {R}) + } + ` + + err := testContractUpdate(t, oldCode, newCode) + cause := getSingleContractUpdateErrorCause(t, err, "Test") + declKindChangeError := &stdlib.InvalidDeclarationKindChangeError{} + require.ErrorAs(t, cause, &declKindChangeError) + }) + + t.Run("struct valid", func(t *testing.T) { + + t.Parallel() + + const oldCode = ` + access(all) contract interface Test { + access(all) struct S {} + access(all) fun foo(r: S) + } + ` + + const newCode = ` + access(all) contract interface Test { + access(all) struct interface S {} + access(all) fun foo(r: {S}) + } + ` + + err := testContractUpdate(t, oldCode, newCode) + require.NoError(t, err) + }) + + t.Run("struct invalid", func(t *testing.T) { + + t.Parallel() + + const oldCode = ` + access(all) contract interface Test { + access(all) struct S {} + access(all) fun foo(r: S) + } + ` + + const newCode = ` + access(all) contract interface Test { + access(all) resource interface S {} + access(all) fun foo(r: @{S}) + } + ` + + err := testContractUpdate(t, oldCode, newCode) + cause := getSingleContractUpdateErrorCause(t, err, "Test") + declKindChangeError := &stdlib.InvalidDeclarationKindChangeError{} + require.ErrorAs(t, cause, &declKindChangeError) + }) +} diff --git a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go index 8c0bbe909..025896a91 100644 --- a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go +++ b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go @@ -257,6 +257,7 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) checkEntitlementsUpgrad func (validator *CadenceV042ToV1ContractUpdateValidator) checkTypeUpgradability(oldType ast.Type, newType ast.Type) error { +typeSwitch: switch oldType := oldType.(type) { case *ast.OptionalType: if newOptional, isOptional := newType.(*ast.OptionalType); isOptional { @@ -282,6 +283,19 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) checkTypeUpgradability( break } validator.currentRestrictedTypeUpgradeRestrictions = oldType.Types + + // If the old restricted type is for AnyStruct/AnyResource, + // require them to drop the "restricted type". + // e.g: `T{I} -> {I}` + if restrictedNominalType, isNominal := oldType.LegacyRestrictedType.(*ast.NominalType); isNominal { + switch restrictedNominalType.Identifier.Identifier { + case "AnyStruct", "AnyResource": + break typeSwitch + } + } + + // Otherwise require them to drop the "restriction". + // e.g: `T{I} -> T` return validator.checkTypeUpgradability(oldType.LegacyRestrictedType, newType) case *ast.VariableSizedType: @@ -349,6 +363,51 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) checkField(oldField *as }) } +func (validator *CadenceV042ToV1ContractUpdateValidator) checkDeclarationKindChange( + oldDeclaration ast.Declaration, + newDeclaration ast.Declaration, +) bool { + // Do not allow converting between different types of composite declarations: + // e.g: - 'contracts' and 'contract-interfaces', + // - 'structs' and 'enums' + // + // However, with the removal of type requirements, it is OK to convert a + // concrete type (Struct or Resource) to an interface type (StructInterface or ResourceInterface). + // However, resource should stay a resource interface, and cannot be a struct interface. + + oldDeclKind := oldDeclaration.DeclarationKind() + newDeclKind := newDeclaration.DeclarationKind() + if oldDeclKind == newDeclKind { + return true + } + + parent := validator.getCurrentDeclaration() + + // If the parent is an interface, and the child is a concrete type, + // then it is a type requirement. + if parent.DeclarationKind() == common.DeclarationKindContractInterface { + // A struct is OK to be converted to a struct-interface + if oldDeclKind == common.DeclarationKindStructure && + newDeclKind == common.DeclarationKindStructureInterface { + return true + } + + // A resource is OK to be converted to a resource-interface + if oldDeclKind == common.DeclarationKindResource && + newDeclKind == common.DeclarationKindResourceInterface { + return true + } + } + + validator.report(&InvalidDeclarationKindChangeError{ + Name: oldDeclaration.DeclarationIdentifier().Identifier, + OldKind: oldDeclaration.DeclarationKind(), + NewKind: newDeclaration.DeclarationKind(), + Range: ast.NewUnmeteredRangeFromPositioned(newDeclaration.DeclarationIdentifier()), + }) + return false +} + // AuthorizationMismatchError is reported during a contract upgrade, // when a field value is given authorization that is more powerful // than that which the migration would grant it diff --git a/runtime/stdlib/contract_update_validation.go b/runtime/stdlib/contract_update_validation.go index 4eb0e0333..484ec5710 100644 --- a/runtime/stdlib/contract_update_validation.go +++ b/runtime/stdlib/contract_update_validation.go @@ -38,6 +38,11 @@ type UpdateValidator interface { checkField(oldField *ast.FieldDeclaration, newField *ast.FieldDeclaration) getAccountContractNames(address common.Address) ([]string, error) + + checkDeclarationKindChange( + oldDeclaration ast.Declaration, + newDeclaration ast.Declaration, + ) bool } type ContractUpdateValidator struct { @@ -187,17 +192,7 @@ func checkDeclarationUpdatability( newDeclaration ast.Declaration, ) { - // Do not allow converting between different types of composite declarations: - // e.g: - 'contracts' and 'contract-interfaces', - // - 'structs' and 'enums' - if oldDeclaration.DeclarationKind() != newDeclaration.DeclarationKind() { - validator.report(&InvalidDeclarationKindChangeError{ - Name: oldDeclaration.DeclarationIdentifier().Identifier, - OldKind: oldDeclaration.DeclarationKind(), - NewKind: newDeclaration.DeclarationKind(), - Range: ast.NewUnmeteredRangeFromPositioned(newDeclaration.DeclarationIdentifier()), - }) - + if !validator.checkDeclarationKindChange(oldDeclaration, newDeclaration) { return } @@ -260,6 +255,27 @@ func (validator *ContractUpdateValidator) checkField(oldField *ast.FieldDeclarat } } +func (validator *ContractUpdateValidator) checkDeclarationKindChange( + oldDeclaration ast.Declaration, + newDeclaration ast.Declaration, +) bool { + // Do not allow converting between different types of composite declarations: + // e.g: - 'contracts' and 'contract-interfaces', + // - 'structs' and 'enums' + if oldDeclaration.DeclarationKind() != newDeclaration.DeclarationKind() { + validator.report(&InvalidDeclarationKindChangeError{ + Name: oldDeclaration.DeclarationIdentifier().Identifier, + OldKind: oldDeclaration.DeclarationKind(), + NewKind: newDeclaration.DeclarationKind(), + Range: ast.NewUnmeteredRangeFromPositioned(newDeclaration.DeclarationIdentifier()), + }) + + return false + } + + return true +} + func checkNestedDeclarations( validator UpdateValidator, oldDeclaration ast.Declaration, @@ -329,6 +345,11 @@ func checkNestedDeclarations( }) for _, declaration := range missingDeclarations { + // OK to remove events - they are not stored + if declaration.DeclarationKind() == common.DeclarationKindEvent { + continue + } + validator.report(&MissingDeclarationError{ Name: declaration.DeclarationIdentifier().Identifier, Kind: declaration.DeclarationKind(), From 08f8977ddaf83bed516d4d6fd318d02377ba1a20 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 16 Feb 2024 11:18:07 +0530 Subject: [PATCH 3/4] Require converting empty set T{} to T --- ..._to_v1_contract_upgrade_validation_test.go | 32 ++++++++++++++++++- ..._v0.42_to_v1_contract_upgrade_validator.go | 15 ++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go index 0019a6789..1522657e7 100644 --- a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go +++ b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go @@ -817,7 +817,7 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { assertFieldTypeMismatchError(t, cause, "Test", "a", "R", "{I}") }) - t.Run("change field type AnyResource restricted type", func(t *testing.T) { + t.Run("AnyResource restricted type, with restrictions", func(t *testing.T) { t.Parallel() @@ -849,6 +849,36 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { require.NoError(t, err) }) + t.Run("AnyResource restricted type, without restrictions", func(t *testing.T) { + + t.Parallel() + + const oldCode = ` + pub contract Test { + pub resource R {} + + pub var a: @AnyResource{} + init() { + self.a <- create R() + } + } + ` + + const newCode = ` + access(all) contract Test { + access(all) resource R {} + + access(all) var a: @AnyResource + init() { + self.a <- create R() + } + } + ` + + err := testContractUpdate(t, oldCode, newCode) + require.NoError(t, err) + }) + t.Run("change field type restricted type variable sized", func(t *testing.T) { t.Parallel() diff --git a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go index 025896a91..d7ab8ae08 100644 --- a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go +++ b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go @@ -285,17 +285,22 @@ typeSwitch: validator.currentRestrictedTypeUpgradeRestrictions = oldType.Types // If the old restricted type is for AnyStruct/AnyResource, - // require them to drop the "restricted type". - // e.g: `T{I} -> {I}` + // and if there are atleast one restriction, require them to drop the "restricted type". + // e.g-1: `AnyStruct{I} -> {I}` + // e.g-2: `AnyResource{I} -> {I}` + // See: https://github.com/onflow/cadence/issues/3112 if restrictedNominalType, isNominal := oldType.LegacyRestrictedType.(*ast.NominalType); isNominal { switch restrictedNominalType.Identifier.Identifier { case "AnyStruct", "AnyResource": - break typeSwitch + if len(oldType.Types) > 0 { + break typeSwitch + } } } - // Otherwise require them to drop the "restriction". - // e.g: `T{I} -> T` + // Otherwise require them to drop the "restrictions". + // e.g-1: `T{I} -> T` + // e.g-2: `AnyStruct{} -> AnyStruct` return validator.checkTypeUpgradability(oldType.LegacyRestrictedType, newType) case *ast.VariableSizedType: From fc35c3804f954a32bc265a739edc7f73e6de72aa Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 20 Feb 2024 16:29:21 +0530 Subject: [PATCH 4/4] Add test for resitricted reference type updates --- ..._to_v1_contract_upgrade_validation_test.go | 870 ++++++++++-------- 1 file changed, 503 insertions(+), 367 deletions(-) diff --git a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go index 1522657e7..e9effda9b 100644 --- a/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go +++ b/runtime/stdlib/cadence_v0.42_to_v1_contract_upgrade_validation_test.go @@ -196,24 +196,24 @@ func TestContractUpgradeFieldAccess(t *testing.T) { const oldCode = ` access(all) contract Test { - access(all) resource R { - access(all) var a: Int - init() { - self.a = 0 - } - } + access(all) resource R { + access(all) var a: Int + init() { + self.a = 0 + } + } } ` const newCode = ` access(all) contract Test { - access(all) entitlement E - access(all) resource R { - access(E) var a: Int - init() { - self.a = 0 - } - } + access(all) entitlement E + access(all) resource R { + access(E) var a: Int + init() { + self.a = 0 + } + } } ` @@ -278,7 +278,7 @@ func TestContractUpgradeFieldType(t *testing.T) { t.Parallel() - t.Run("change field types illegally", func(t *testing.T) { + t.Run("simple invalid", func(t *testing.T) { t.Parallel() @@ -292,12 +292,12 @@ func TestContractUpgradeFieldType(t *testing.T) { ` const newCode = ` - access(all) contract Test { - access(all) var a: String - init() { - self.a = "hello" - } - } + access(all) contract Test { + access(all) var a: String + init() { + self.a = "hello" + } + } ` err := testContractUpdate(t, oldCode, newCode) @@ -307,15 +307,15 @@ func TestContractUpgradeFieldType(t *testing.T) { }) - t.Run("change field intersection types illegally", func(t *testing.T) { + t.Run("intersection types invalid", func(t *testing.T) { t.Parallel() const oldCode = ` access(all) contract Test { - access(all) struct interface I {} - access(all) struct interface J {} - access(all) struct S: I, J {} + access(all) struct interface I {} + access(all) struct interface J {} + access(all) struct S: I, J {} access(all) var a: {I} init() { @@ -325,16 +325,16 @@ func TestContractUpgradeFieldType(t *testing.T) { ` const newCode = ` - access(all) contract Test { - access(all) struct interface I {} - access(all) struct interface J {} - access(all) struct S: I, J {} + access(all) contract Test { + access(all) struct interface I {} + access(all) struct interface J {} + access(all) struct S: I, J {} access(all) var a: {I, J} init() { self.a = S() } - } + } ` err := testContractUpdate(t, oldCode, newCode) @@ -344,7 +344,7 @@ func TestContractUpgradeFieldType(t *testing.T) { }) - t.Run("change field type capability reference auth", func(t *testing.T) { + t.Run("capability reference auth", func(t *testing.T) { t.Parallel() @@ -359,7 +359,7 @@ func TestContractUpgradeFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) entitlement E + access(all) entitlement E access(all) var a: Capability? init() { self.a = nil @@ -373,15 +373,15 @@ func TestContractUpgradeFieldType(t *testing.T) { assertFieldAuthorizationMismatchError(t, cause, "Test", "a", "all", "E") }) - t.Run("change field type capability reference auth allowed composite", func(t *testing.T) { + t.Run("capability reference auth allowed composite", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub struct S { - pub fun foo() {} - } + pub struct S { + pub fun foo() {} + } pub var a: Capability<&S>? init() { @@ -392,11 +392,11 @@ func TestContractUpgradeFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) entitlement E + access(all) entitlement E - access(all) struct S { - access(E) fun foo() {} - } + access(all) struct S { + access(E) fun foo() {} + } access(all) var a: Capability? init() { @@ -410,15 +410,15 @@ func TestContractUpgradeFieldType(t *testing.T) { require.NoError(t, err) }) - t.Run("change field type capability reference auth allowed too many entitlements", func(t *testing.T) { + t.Run("capability reference auth allowed too many entitlements", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub struct S { - pub fun foo() {} - } + pub struct S { + pub fun foo() {} + } pub var a: Capability<&S>? init() { @@ -429,12 +429,12 @@ func TestContractUpgradeFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) entitlement E - access(all) entitlement F + access(all) entitlement E + access(all) entitlement F - access(all) struct S { - access(E) fun foo() {} - } + access(all) struct S { + access(E) fun foo() {} + } access(all) var a: Capability? init() { @@ -449,16 +449,16 @@ func TestContractUpgradeFieldType(t *testing.T) { assertFieldAuthorizationMismatchError(t, cause, "Test", "a", "E", "E, F") }) - t.Run("change field type capability reference auth fewer entitlements", func(t *testing.T) { + t.Run("capability reference auth fewer entitlements", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub struct S { - pub fun foo() {} - pub fun bar() {} - } + pub struct S { + pub fun foo() {} + pub fun bar() {} + } pub var a: Capability<&S>? init() { @@ -469,13 +469,13 @@ func TestContractUpgradeFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) entitlement E - access(all) entitlement F + access(all) entitlement E + access(all) entitlement F - access(all) struct S { - access(E) fun foo() {} - access(F) fun bar() {} - } + access(all) struct S { + access(E) fun foo() {} + access(F) fun bar() {} + } access(all) var a: Capability? init() { @@ -489,16 +489,16 @@ func TestContractUpgradeFieldType(t *testing.T) { require.NoError(t, err) }) - t.Run("change field type capability reference auth disjunctive entitlements", func(t *testing.T) { + t.Run("capability reference auth disjunctive entitlements", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub struct S { - pub fun foo() {} - pub fun bar() {} - } + pub struct S { + pub fun foo() {} + pub fun bar() {} + } pub var a: Capability<&S>? init() { @@ -509,13 +509,13 @@ func TestContractUpgradeFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) entitlement E - access(all) entitlement F + access(all) entitlement E + access(all) entitlement F - access(all) struct S { - access(E) fun foo() {} - access(F) fun bar() {} - } + access(all) struct S { + access(E) fun foo() {} + access(F) fun bar() {} + } access(all) var a: Capability? init() { @@ -539,39 +539,39 @@ func TestContractUpgradeIntersectionAuthorization(t *testing.T) { t.Parallel() const oldCode = ` - pub contract Test { - pub struct interface I { - pub fun foo() - } - pub struct S:I { - pub fun foo() {} - } - - pub var a: Capability<&{I}>? - init() { - self.a = nil - } - } - ` + pub contract Test { + pub struct interface I { + pub fun foo() + } + pub struct S:I { + pub fun foo() {} + } + + pub var a: Capability<&{I}>? + init() { + self.a = nil + } + } + ` const newCode = ` - access(all) contract Test { - access(all) entitlement E + access(all) contract Test { + access(all) entitlement E - access(all) struct interface I { - access(E) fun foo() - } + access(all) struct interface I { + access(E) fun foo() + } - access(all) struct S:I { - access(E) fun foo() {} - } + access(all) struct S:I { + access(E) fun foo() {} + } - access(all) var a: Capability? - init() { - self.a = nil - } - } - ` + access(all) var a: Capability? + init() { + self.a = nil + } + } + ` err := testContractUpdate(t, oldCode, newCode) @@ -583,35 +583,35 @@ func TestContractUpgradeIntersectionAuthorization(t *testing.T) { t.Parallel() const oldCode = ` - pub contract Test { - pub struct interface I {} - pub struct S:I { - pub fun foo() {} - } - - pub var a: Capability<&{I}>? - init() { - self.a = nil - } - } - ` + pub contract Test { + pub struct interface I {} + pub struct S:I { + pub fun foo() {} + } + + pub var a: Capability<&{I}>? + init() { + self.a = nil + } + } + ` const newCode = ` - access(all) contract Test { - access(all) entitlement E + access(all) contract Test { + access(all) entitlement E - access(all) struct interface I {} + access(all) struct interface I {} - access(all) struct S:I { - access(E) fun foo() {} - } + access(all) struct S:I { + access(E) fun foo() {} + } - access(all) var a: Capability? - init() { - self.a = nil - } - } - ` + access(all) var a: Capability? + init() { + self.a = nil + } + } + ` err := testContractUpdate(t, oldCode, newCode) @@ -624,48 +624,48 @@ func TestContractUpgradeIntersectionAuthorization(t *testing.T) { t.Parallel() const oldCode = ` - pub contract Test { - pub struct interface I { - pub fun bar() - } - pub struct interface J { - pub fun foo() - } - pub struct S:I, J { - pub fun foo() {} - pub fun bar() {} - } - - pub var a: Capability<&{I, J}>? - init() { - self.a = nil - } - } - ` + pub contract Test { + pub struct interface I { + pub fun bar() + } + pub struct interface J { + pub fun foo() + } + pub struct S:I, J { + pub fun foo() {} + pub fun bar() {} + } + + pub var a: Capability<&{I, J}>? + init() { + self.a = nil + } + } + ` const newCode = ` - access(all) contract Test { - access(all) entitlement E - access(all) entitlement F - - access(all) struct interface I { - access(E) fun foo() - } - access(all) struct interface J { - access(F) fun bar() - } - - access(all) struct S:I, J { - access(E) fun foo() {} - access(F) fun bar() {} - } - - access(all) var a: Capability? - init() { - self.a = nil - } - } - ` + access(all) contract Test { + access(all) entitlement E + access(all) entitlement F + + access(all) struct interface I { + access(E) fun foo() + } + access(all) struct interface J { + access(F) fun bar() + } + + access(all) struct S:I, J { + access(E) fun foo() {} + access(F) fun bar() {} + } + + access(all) var a: Capability? + init() { + self.a = nil + } + } + ` err := testContractUpdate(t, oldCode, newCode) @@ -677,48 +677,48 @@ func TestContractUpgradeIntersectionAuthorization(t *testing.T) { t.Parallel() const oldCode = ` - pub contract Test { - pub struct interface I { - pub fun bar() - } - pub struct interface J { - pub fun foo() - } - pub struct S:I, J { - pub fun foo() {} - pub fun bar() {} - } - - pub var a: Capability<&{I, J}>? - init() { - self.a = nil - } - } - ` + pub contract Test { + pub struct interface I { + pub fun bar() + } + pub struct interface J { + pub fun foo() + } + pub struct S:I, J { + pub fun foo() {} + pub fun bar() {} + } + + pub var a: Capability<&{I, J}>? + init() { + self.a = nil + } + } + ` const newCode = ` - access(all) contract Test { - access(all) entitlement E - access(all) entitlement F - - access(all) struct interface I { - access(E) fun foo() - } - access(all) struct interface J { - access(F) fun bar() - } - - access(all) struct S:I, J { - access(E) fun foo() {} - access(F) fun bar() {} - } - - access(all) var a: Capability? - init() { - self.a = nil - } - } - ` + access(all) contract Test { + access(all) entitlement E + access(all) entitlement F + + access(all) struct interface I { + access(E) fun foo() + } + access(all) struct interface J { + access(F) fun bar() + } + + access(all) struct S:I, J { + access(E) fun foo() {} + access(F) fun bar() {} + } + + access(all) var a: Capability? + init() { + self.a = nil + } + } + ` err := testContractUpdate(t, oldCode, newCode) @@ -730,45 +730,45 @@ func TestContractUpgradeIntersectionAuthorization(t *testing.T) { t.Parallel() const oldCode = ` - pub contract Test { - pub struct interface I { - pub fun bar() - } - pub struct interface J { - pub fun foo() - } - pub struct S:I, J { - pub fun foo() {} - pub fun bar() {} - } - - pub var a: Capability<&{I, J}>? - init() { - self.a = nil - } - } - ` + pub contract Test { + pub struct interface I { + pub fun bar() + } + pub struct interface J { + pub fun foo() + } + pub struct S:I, J { + pub fun foo() {} + pub fun bar() {} + } + + pub var a: Capability<&{I, J}>? + init() { + self.a = nil + } + } + ` const newCode = ` - access(all) contract Test { - access(all) entitlement E - access(all) entitlement F - - access(all) struct interface I { - access(E) fun foo() - } - access(all) struct interface J {} - - access(all) struct S:I, J { - access(E) fun foo() {} - } - - access(all) var a: Capability? - init() { - self.a = nil - } - } - ` + access(all) contract Test { + access(all) entitlement E + access(all) entitlement F + + access(all) struct interface I { + access(E) fun foo() + } + access(all) struct interface J {} + + access(all) struct S:I, J { + access(E) fun foo() {} + } + + access(all) var a: Capability? + init() { + self.a = nil + } + } + ` err := testContractUpdate(t, oldCode, newCode) @@ -782,14 +782,14 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { t.Parallel() - t.Run("change field type restricted type", func(t *testing.T) { + t.Run("restricted type", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub resource interface I {} - pub resource R:I {} + pub resource interface I {} + pub resource R:I {} pub var a: @R{I} init() { @@ -800,8 +800,8 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) resource interface I {} - access(all) resource R:I {} + access(all) resource interface I {} + access(all) resource R:I {} access(all) var a: @{I} init() { @@ -879,14 +879,14 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { require.NoError(t, err) }) - t.Run("change field type restricted type variable sized", func(t *testing.T) { + t.Run("restricted type variable sized", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub resource interface I {} - pub resource R:I {} + pub resource interface I {} + pub resource R:I {} pub var a: @[R{I}] init() { @@ -897,8 +897,8 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) resource interface I {} - access(all) resource R:I {} + access(all) resource interface I {} + access(all) resource R:I {} access(all) var a: @[R] init() { @@ -912,14 +912,14 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { require.NoError(t, err) }) - t.Run("change field type restricted type constant sized", func(t *testing.T) { + t.Run("restricted type constant sized", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub resource interface I {} - pub resource R:I {} + pub resource interface I {} + pub resource R:I {} pub var a: @[R{I}; 1] init() { @@ -930,8 +930,8 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) resource interface I {} - access(all) resource R:I {} + access(all) resource interface I {} + access(all) resource R:I {} access(all) var a: @[R; 1] init() { @@ -945,14 +945,14 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { require.NoError(t, err) }) - t.Run("change field type restricted type constant sized with size change", func(t *testing.T) { + t.Run("restricted type constant sized with size change", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub resource interface I {} - pub resource R:I {} + pub resource interface I {} + pub resource R:I {} pub var a: @[R{I}; 1] init() { @@ -963,8 +963,8 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) resource interface I {} - access(all) resource R:I {} + access(all) resource interface I {} + access(all) resource R:I {} access(all) var a: @[R; 2] init() { @@ -979,14 +979,14 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { assertFieldTypeMismatchError(t, cause, "Test", "a", "[{I}; 1]", "[R; 2]") }) - t.Run("change field type restricted type dict", func(t *testing.T) { + t.Run("restricted type dict", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub resource interface I {} - pub resource R:I {} + pub resource interface I {} + pub resource R:I {} pub var a: @{Int: R{I}} init() { @@ -997,8 +997,8 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) resource interface I {} - access(all) resource R:I {} + access(all) resource interface I {} + access(all) resource R:I {} access(all) var a: @{Int: R} init() { @@ -1012,14 +1012,14 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { require.NoError(t, err) }) - t.Run("change field type restricted type dict with qualified names", func(t *testing.T) { + t.Run("restricted type dict with qualified names", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub resource interface I {} - pub resource R:I {} + pub resource interface I {} + pub resource R:I {} pub var a: @{Int: R{I}} init() { @@ -1030,8 +1030,8 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) resource interface I {} - access(all) resource R:I {} + access(all) resource interface I {} + access(all) resource R:I {} access(all) var a: @{Int: Test.R} init() { @@ -1045,14 +1045,14 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { require.NoError(t, err) }) - t.Run("change field type restricted reference type", func(t *testing.T) { + t.Run("restricted reference type", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub resource interface I {} - pub resource R:I {} + pub resource interface I {} + pub resource R:I {} pub var a: Capability<&R{I}>? init() { @@ -1063,8 +1063,8 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) resource interface I {} - access(all) resource R:I {} + access(all) resource interface I {} + access(all) resource R:I {} access(all) var a: Capability<&R>? init() { @@ -1078,18 +1078,18 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { require.NoError(t, err) }) - t.Run("change field type restricted entitled reference type", func(t *testing.T) { + t.Run("restricted entitled reference type", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub resource interface I { - pub fun foo() - } - pub resource R:I { - pub fun foo() - } + pub resource interface I { + pub fun foo() + } + pub resource R:I { + pub fun foo() + } pub var a: Capability<&R{I}>? init() { @@ -1100,13 +1100,13 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) entitlement E - access(all) resource interface I { - access(E) fun foo() - } - access(all) resource R:I { - access(E) fun foo() {} - } + access(all) entitlement E + access(all) resource interface I { + access(E) fun foo() + } + access(all) resource R:I { + access(E) fun foo() {} + } access(all) var a: Capability? init() { @@ -1120,18 +1120,18 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { require.NoError(t, err) }) - t.Run("change field type restricted entitled reference type with qualified types", func(t *testing.T) { + t.Run("restricted entitled reference type with qualified types", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub resource interface I { - pub fun foo() - } - pub resource R:I { - pub fun foo() - } + pub resource interface I { + pub fun foo() + } + pub resource R:I { + pub fun foo() + } pub var a: Capability<&R{I}>? init() { @@ -1142,13 +1142,13 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) entitlement E - access(all) resource interface I { - access(Test.E) fun foo() - } - access(all) resource R:I { - access(Test.E) fun foo() {} - } + access(all) entitlement E + access(all) resource interface I { + access(Test.E) fun foo() + } + access(all) resource R:I { + access(Test.E) fun foo() {} + } access(all) var a: Capability? init() { @@ -1162,25 +1162,25 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { require.NoError(t, err) }) - t.Run("change field type restricted entitled reference type with qualified types with imports", func(t *testing.T) { + t.Run("restricted entitled reference type with qualified types with imports", func(t *testing.T) { t.Parallel() const oldImport = ` - pub contract TestImport { - pub resource interface I { - pub fun foo() - } - } - ` + pub contract TestImport { + pub resource interface I { + pub fun foo() + } + } + ` const oldCode = ` - import TestImport from "imported" + import TestImport from "imported" pub contract Test { - pub resource R:TestImport.I { - pub fun foo() - } + pub resource R:TestImport.I { + pub fun foo() + } pub var a: Capability<&R{TestImport.I}>? init() { @@ -1190,23 +1190,23 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { ` const newImport = ` - access(all) contract TestImport { - access(all) entitlement E - access(all) resource interface I { - access(E) fun foo() - } - } - ` + access(all) contract TestImport { + access(all) entitlement E + access(all) resource interface I { + access(E) fun foo() + } + } + ` const newCode = ` - import TestImport from "imported" + import TestImport from "imported" access(all) contract Test { - access(all) entitlement F - access(all) resource R: TestImport.I { - access(TestImport.E) fun foo() {} - access(Test.F) fun bar() {} - } + access(all) entitlement F + access(all) resource R: TestImport.I { + access(TestImport.E) fun foo() {} + access(Test.F) fun bar() {} + } access(all) var a: Capability? init() { @@ -1220,19 +1220,19 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { require.NoError(t, err) }) - t.Run("change field type restricted entitled reference type with too many granted entitlements", func(t *testing.T) { + t.Run("restricted entitled reference type with too many granted entitlements", func(t *testing.T) { t.Parallel() const oldCode = ` pub contract Test { - pub resource interface I { - pub fun foo() - } - pub resource R:I { - pub fun foo() - pub fun bar() - } + pub resource interface I { + pub fun foo() + } + pub resource R:I { + pub fun foo() + pub fun bar() + } pub var a: Capability<&R{I}>? init() { @@ -1243,15 +1243,15 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { const newCode = ` access(all) contract Test { - access(all) entitlement E - access(all) entitlement F - access(all) resource interface I { - access(E) fun foo() - } - access(all) resource R:I { - access(E) fun foo() {} - access(F) fun bar() {} - } + access(all) entitlement E + access(all) entitlement F + access(all) resource interface I { + access(E) fun foo() + } + access(all) resource R:I { + access(E) fun foo() {} + access(F) fun bar() {} + } access(all) var a: Capability? init() { @@ -1266,25 +1266,25 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { assertFieldAuthorizationMismatchError(t, cause, "Test", "a", "E", "E, F") }) - t.Run("change field type restricted entitled reference type with too many granted entitlements with imports", func(t *testing.T) { + t.Run("restricted entitled reference type with too many granted entitlements with imports", func(t *testing.T) { t.Parallel() const oldImport = ` - pub contract TestImport { - pub resource interface I { - pub fun foo() - } - } - ` + pub contract TestImport { + pub resource interface I { + pub fun foo() + } + } + ` const oldCode = ` - import TestImport from "imported" + import TestImport from "imported" pub contract Test { - pub resource R:TestImport.I { - pub fun foo() - } + pub resource R:TestImport.I { + pub fun foo() + } pub var a: Capability<&R{TestImport.I}>? init() { @@ -1294,23 +1294,23 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { ` const newImport = ` - access(all) contract TestImport { - access(all) entitlement E - access(all) resource interface I { - access(TestImport.E) fun foo() - } - } - ` + access(all) contract TestImport { + access(all) entitlement E + access(all) resource interface I { + access(TestImport.E) fun foo() + } + } + ` const newCode = ` - import TestImport from "imported" + import TestImport from "imported" access(all) contract Test { - access(all) entitlement F - access(all) resource R: TestImport.I { - access(TestImport.E) fun foo() {} - access(Test.F) fun bar() {} - } + access(all) entitlement F + access(all) resource R: TestImport.I { + access(TestImport.E) fun foo() {} + access(Test.F) fun bar() {} + } access(all) var a: Capability? init() { @@ -1324,6 +1324,142 @@ func TestContractUpgradeIntersectionFieldType(t *testing.T) { cause := getSingleContractUpdateErrorCause(t, err, "Test") assertFieldAuthorizationMismatchError(t, cause, "Test", "a", "E", "E, F") }) + + t.Run("restricted reference type", func(t *testing.T) { + + t.Parallel() + + const oldCode = ` + pub contract Test { + + pub resource interface I {} + + pub resource R:I { + access(all) var ref: &R{I}? + + init() { + self.ref = nil + } + } + } + ` + + const newCode = ` + access(all) contract Test { + + access(all) resource interface I { + access(E) fun foo() + } + + access(all) entitlement E + + access(all) resource R:I { + access(all) var ref: auth(E) &R? + + init() { + self.ref = nil + } + + access(E) fun foo() {} + } + } + ` + + err := testContractUpdate(t, oldCode, newCode) + require.NoError(t, err) + }) + + t.Run("restricted anystruct reference type invalid", func(t *testing.T) { + + t.Parallel() + + const oldCode = ` + pub contract Test { + + pub resource interface I {} + + pub resource R:I { + access(all) var ref: &AnyStruct{I}? + + init() { + self.ref = nil + } + } + } + ` + + const newCode = ` + access(all) contract Test { + + access(all) resource interface I { + access(E) fun foo() + } + + access(all) entitlement E + + access(all) resource R:I { + access(all) var ref: auth(E) &AnyStruct? + + init() { + self.ref = nil + } + + access(E) fun foo() {} + } + } + ` + + err := testContractUpdate(t, oldCode, newCode) + + cause := getSingleContractUpdateErrorCause(t, err, "Test") + assertFieldTypeMismatchError(t, cause, "R", "ref", "{I}", "AnyStruct") + + }) + + t.Run("restricted anystruct reference type valid", func(t *testing.T) { + + t.Parallel() + + const oldCode = ` + pub contract Test { + + pub resource interface I {} + + pub resource R:I { + access(all) var ref: &AnyStruct{I}? + + init() { + self.ref = nil + } + } + } + ` + + const newCode = ` + access(all) contract Test { + + access(all) resource interface I { + access(E) fun foo() + } + + access(all) entitlement E + + access(all) resource R:I { + access(all) var ref: auth(E) &{I}? + + init() { + self.ref = nil + } + + access(E) fun foo() {} + } + } + ` + + err := testContractUpdate(t, oldCode, newCode) + require.NoError(t, err) + }) + } func TestTypeRequirementRemoval(t *testing.T) {