From 69db23c704f56e5a6d814934b3c1b5d5a0d2b815 Mon Sep 17 00:00:00 2001 From: turbolent Date: Tue, 30 Apr 2024 19:13:04 +0000 Subject: [PATCH 01/10] v1.0.0-preview.24 --- npm-packages/cadence-parser/package.json | 2 +- version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/npm-packages/cadence-parser/package.json b/npm-packages/cadence-parser/package.json index 2067af1e90..99e8e349cd 100644 --- a/npm-packages/cadence-parser/package.json +++ b/npm-packages/cadence-parser/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/cadence-parser", - "version": "1.0.0-preview.23", + "version": "1.0.0-preview.24", "description": "The Cadence parser", "homepage": "https://github.com/onflow/cadence", "repository": { diff --git a/version.go b/version.go index 4d99a11e0f..2f275547b5 100644 --- a/version.go +++ b/version.go @@ -21,4 +21,4 @@ package cadence -const Version = "v1.0.0-preview.23" +const Version = "v1.0.0-preview.24" From ab8f875c78a207ed34acdc7c42fbcd09fd7cb630 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 May 2024 12:01:35 -0400 Subject: [PATCH 02/10] add function to check if a set is minimally representable --- runtime/common/orderedmap/orderedmap.go | 3 + runtime/sema/entitlementset.go | 40 ++++- runtime/sema/entitlementset_test.go | 203 ++++++++++++++++++++++++ 3 files changed, 244 insertions(+), 2 deletions(-) diff --git a/runtime/common/orderedmap/orderedmap.go b/runtime/common/orderedmap/orderedmap.go index 462b06ca77..1f584ff886 100644 --- a/runtime/common/orderedmap/orderedmap.go +++ b/runtime/common/orderedmap/orderedmap.go @@ -150,6 +150,9 @@ func (om *OrderedMap[K, V]) Delete(key K) (oldValue V, present bool) { // Len returns the length of the ordered map. func (om *OrderedMap[K, V]) Len() int { + if om == nil { + return 0 + } return len(om.pairs) } diff --git a/runtime/sema/entitlementset.go b/runtime/sema/entitlementset.go index 986098f786..7e36f1d646 100644 --- a/runtime/sema/entitlementset.go +++ b/runtime/sema/entitlementset.go @@ -48,12 +48,16 @@ func disjunctionKey(disjunction *EntitlementOrderedSet) string { type DisjunctionOrderedSet = orderedmap.OrderedMap[string, *EntitlementOrderedSet] // EntitlementSet is a set (conjunction) of entitlements and entitlement disjunctions. -// e.g. {entitlements: A, B; disjunctions: (C | D), (E | F)} +// e.g. {entitlements: A, B; disjunctions: (C ∨ D), (E ∨ F)} +// This is distinct from an EntitlementSetAccess in Cadence, which is a program-level +// (and possibly non-minimal) approximation of this abstract set type EntitlementSet struct { // Entitlements is a set of entitlements Entitlements *EntitlementOrderedSet // Disjunctions is a set of entitlement disjunctions, keyed by disjunctionKey Disjunctions *DisjunctionOrderedSet + // Minimized tracks whether the set is minimized or not + minimized bool } // Add adds an entitlement to the set. @@ -67,6 +71,8 @@ func (s *EntitlementSet) Add(entitlementType *EntitlementType) { s.Entitlements = orderedmap.New[EntitlementOrderedSet](1) } s.Entitlements.Set(entitlementType, struct{}{}) + + s.minimized = false } // AddDisjunction adds an entitlement disjunction to the set. @@ -92,6 +98,8 @@ func (s *EntitlementSet) AddDisjunction(disjunction *EntitlementOrderedSet) { s.Disjunctions = orderedmap.New[DisjunctionOrderedSet](1) } s.Disjunctions.Set(key, disjunction) + + s.minimized = false } // Merge merges the other entitlement set into this set. @@ -116,12 +124,16 @@ func (s *EntitlementSet) Merge(other *EntitlementSet) { s.AddDisjunction(disjunction) }) } + + s.minimized = false } // Minimize minimizes the entitlement set. // It removes disjunctions that contain entitlements // which are also in the entitlement set func (s *EntitlementSet) Minimize() { + s.minimized = true + // If there are no entitlements or no disjunctions, // there is nothing to minimize if s.Entitlements == nil || s.Disjunctions == nil { @@ -141,14 +153,38 @@ func (s *EntitlementSet) Minimize() { } } +// Returns whether this entitlement set is minimally representable in Cadence. +// +// If true, this set can be exactly represented as a non-nested logical formula: i.e. either a single conjunction or a single disjunction +// If false, this set cannot be represented without nesting connective operators, and thus must be over-approximated when being +// represented in Cadence. +// As Cadence does not support nesting disjunctions and conjunctions in the same entitlement set, this function returns false +// when s.Entitlements and s.Disjunctions are both non-empty, or when s.Disjunctions has more than one element +func (s *EntitlementSet) IsMinimallyRepresentable() bool { + if s == nil { + return true + } + + if !s.minimized { + s.Minimize() + } + + return s.Disjunctions.Len() == 0 || (s.Entitlements.Len() == 0 && s.Disjunctions.Len() == 1) +} + // Access returns the access represented by the entitlement set. // The set is minimized before the access is computed. +// Note that this function may over-approximate the permissions +// required to represent this set of entitlements as an access modifier that Cadence can use, +// e.g. `(A ∨ B) ∧ C` cannot be represented in Cadence and will the produce an over-approximation of `(A, B, C)` func (s *EntitlementSet) Access() Access { if s == nil { return UnauthorizedAccess } - s.Minimize() + if !s.minimized { + s.Minimize() + } var entitlements *EntitlementOrderedSet if s.Entitlements != nil && s.Entitlements.Len() > 0 { diff --git a/runtime/sema/entitlementset_test.go b/runtime/sema/entitlementset_test.go index ec5d0d80ad..dab1054537 100644 --- a/runtime/sema/entitlementset_test.go +++ b/runtime/sema/entitlementset_test.go @@ -40,6 +40,7 @@ func TestEntitlementSet_Add(t *testing.T) { } set.Add(e1) + assert.False(t, set.minimized) assert.Equal(t, 1, set.Entitlements.Len()) assert.Nil(t, set.Disjunctions) @@ -48,6 +49,7 @@ func TestEntitlementSet_Add(t *testing.T) { } set.Add(e2) + assert.False(t, set.minimized) assert.Equal(t, 2, set.Entitlements.Len()) assert.Nil(t, set.Disjunctions) }) @@ -70,6 +72,7 @@ func TestEntitlementSet_Add(t *testing.T) { set.AddDisjunction(e1e2) + assert.False(t, set.minimized) assert.Nil(t, set.Entitlements) assert.Equal(t, 1, set.Disjunctions.Len()) @@ -77,6 +80,7 @@ func TestEntitlementSet_Add(t *testing.T) { set.Add(e2) + assert.False(t, set.minimized) assert.Equal(t, 1, set.Entitlements.Len()) // NOTE: the set is not minimal, // the disjunction is not discarded @@ -108,6 +112,7 @@ func TestEntitlementSet_AddDisjunction(t *testing.T) { set.AddDisjunction(e1e2) + assert.False(t, set.minimized) assert.Nil(t, set.Entitlements) assert.Equal(t, 1, set.Disjunctions.Len()) @@ -115,6 +120,7 @@ func TestEntitlementSet_AddDisjunction(t *testing.T) { set.AddDisjunction(e1e2) + assert.False(t, set.minimized) assert.Nil(t, set.Entitlements) assert.Equal(t, 1, set.Disjunctions.Len()) @@ -126,6 +132,7 @@ func TestEntitlementSet_AddDisjunction(t *testing.T) { set.AddDisjunction(e2e1) + assert.False(t, set.minimized) assert.Nil(t, set.Entitlements) assert.Equal(t, 1, set.Disjunctions.Len()) @@ -141,6 +148,7 @@ func TestEntitlementSet_AddDisjunction(t *testing.T) { set.AddDisjunction(e2e3) + assert.False(t, set.minimized) assert.Nil(t, set.Entitlements) assert.Equal(t, 2, set.Disjunctions.Len()) }) @@ -156,6 +164,7 @@ func TestEntitlementSet_AddDisjunction(t *testing.T) { set.Add(e1) + assert.False(t, set.minimized) assert.Equal(t, 1, set.Entitlements.Len()) assert.Nil(t, set.Disjunctions) @@ -171,6 +180,7 @@ func TestEntitlementSet_AddDisjunction(t *testing.T) { set.AddDisjunction(e1e2) + assert.False(t, set.minimized) assert.Equal(t, 1, set.Entitlements.Len()) assert.Nil(t, set.Disjunctions) }) @@ -206,6 +216,7 @@ func TestEntitlementSet_Merge(t *testing.T) { set1.Add(e1) set1.AddDisjunction(e2e3) + assert.False(t, set1.minimized) assert.Equal(t, 1, set1.Entitlements.Len()) assert.Equal(t, 1, set1.Disjunctions.Len()) @@ -215,6 +226,7 @@ func TestEntitlementSet_Merge(t *testing.T) { set2.Add(e2) set2.AddDisjunction(e3e4) + assert.False(t, set2.minimized) assert.Equal(t, 1, set2.Entitlements.Len()) assert.Equal(t, 1, set2.Disjunctions.Len()) @@ -222,6 +234,7 @@ func TestEntitlementSet_Merge(t *testing.T) { set1.Merge(set2) + assert.False(t, set1.minimized) assert.Equal(t, 2, set1.Entitlements.Len()) assert.True(t, set1.Entitlements.Contains(e1)) assert.True(t, set1.Entitlements.Contains(e2)) @@ -257,6 +270,7 @@ func TestEntitlementSet_Minimize(t *testing.T) { set.Add(e1) // NOTE: the set is not minimal + assert.False(t, set.minimized) assert.Equal(t, 1, set.Entitlements.Len()) assert.Equal(t, 1, set.Disjunctions.Len()) @@ -264,10 +278,176 @@ func TestEntitlementSet_Minimize(t *testing.T) { set.Minimize() + assert.True(t, set.minimized) assert.Equal(t, 1, set.Entitlements.Len()) assert.Equal(t, 0, set.Disjunctions.Len()) } +func TestEntitlementSet_MinimallyRepresentable(t *testing.T) { + t.Parallel() + + t.Run("no entitlements, no disjunctions", func(t *testing.T) { + t.Parallel() + + set := &EntitlementSet{} + assert.True(t, set.IsMinimallyRepresentable()) + }) + + t.Run("one entitlement, no disjunctions", func(t *testing.T) { + t.Parallel() + + set := &EntitlementSet{} + + e1 := &EntitlementType{ + Identifier: "E1", + } + set.Add(e1) + + assert.True(t, set.IsMinimallyRepresentable()) + }) + + t.Run("two entitlements, no disjunctions", func(t *testing.T) { + t.Parallel() + + set := &EntitlementSet{} + + e1 := &EntitlementType{ + Identifier: "E1", + } + set.Add(e1) + + e2 := &EntitlementType{ + Identifier: "E2", + } + set.Add(e2) + + assert.True(t, set.IsMinimallyRepresentable()) + }) + + t.Run("one entitlement, redundant disjunction", func(t *testing.T) { + t.Parallel() + + set := &EntitlementSet{} + + e1 := &EntitlementType{ + Identifier: "E1", + } + + e2 := &EntitlementType{ + Identifier: "E2", + } + + set.Add(e1) + + e1e2 := orderedmap.New[EntitlementOrderedSet](2) + e1e2.Set(e1, struct{}{}) + e1e2.Set(e2, struct{}{}) + + set.AddDisjunction(e1e2) + + assert.True(t, set.IsMinimallyRepresentable()) + }) + + t.Run("two entitlements, two redundant disjunctions", func(t *testing.T) { + t.Parallel() + + set := &EntitlementSet{} + + e1 := &EntitlementType{ + Identifier: "E1", + } + + e2 := &EntitlementType{ + Identifier: "E2", + } + + e3 := &EntitlementType{ + Identifier: "E1", + } + + e4 := &EntitlementType{ + Identifier: "E2", + } + + set.Add(e1) + set.Add(e3) + + e1e2 := orderedmap.New[EntitlementOrderedSet](2) + e1e2.Set(e1, struct{}{}) + e1e2.Set(e2, struct{}{}) + + set.AddDisjunction(e1e2) + + e3e4 := orderedmap.New[EntitlementOrderedSet](2) + e3e4.Set(e3, struct{}{}) + e3e4.Set(e4, struct{}{}) + + set.AddDisjunction(e3e4) + + assert.True(t, set.IsMinimallyRepresentable()) + }) + + t.Run("one entitlement, non-redundant disjunction", func(t *testing.T) { + t.Parallel() + + set := &EntitlementSet{} + + e1 := &EntitlementType{ + Identifier: "E1", + } + + e2 := &EntitlementType{ + Identifier: "E2", + } + + e3 := &EntitlementType{ + Identifier: "E3", + } + + set.Add(e1) + + e3e2 := orderedmap.New[EntitlementOrderedSet](2) + e3e2.Set(e3, struct{}{}) + e3e2.Set(e2, struct{}{}) + + set.AddDisjunction(e3e2) + + assert.False(t, set.IsMinimallyRepresentable()) + }) + + t.Run("two disjunctions", func(t *testing.T) { + t.Parallel() + + set := &EntitlementSet{} + + e1 := &EntitlementType{ + Identifier: "E1", + } + + e2 := &EntitlementType{ + Identifier: "E2", + } + + e3 := &EntitlementType{ + Identifier: "E3", + } + + e1e2 := orderedmap.New[EntitlementOrderedSet](2) + e1e2.Set(e1, struct{}{}) + e1e2.Set(e2, struct{}{}) + + set.AddDisjunction(e1e2) + + e3e2 := orderedmap.New[EntitlementOrderedSet](2) + e3e2.Set(e3, struct{}{}) + e3e2.Set(e2, struct{}{}) + + set.AddDisjunction(e3e2) + + assert.False(t, set.IsMinimallyRepresentable()) + }) +} + func TestEntitlementSet_Access(t *testing.T) { t.Parallel() @@ -278,6 +458,7 @@ func TestEntitlementSet_Access(t *testing.T) { access := set.Access() + assert.True(t, set.minimized) assert.Equal(t, UnauthorizedAccess, access) }) @@ -296,7 +477,9 @@ func TestEntitlementSet_Access(t *testing.T) { } set.Add(e2) + assert.False(t, set.minimized) access := set.Access() + assert.True(t, set.minimized) expectedEntitlements := orderedmap.New[EntitlementOrderedSet](2) expectedEntitlements.Set(e1, struct{}{}) @@ -329,7 +512,9 @@ func TestEntitlementSet_Access(t *testing.T) { set.AddDisjunction(e1e2) + assert.False(t, set.minimized) access := set.Access() + assert.True(t, set.minimized) assert.Equal(t, EntitlementSetAccess{ @@ -364,12 +549,20 @@ func TestEntitlementSet_Access(t *testing.T) { e2e3.Set(e3, struct{}{}) set.AddDisjunction(e1e2) + + assert.False(t, set.minimized) + set.Minimize() + assert.True(t, set.minimized) + set.AddDisjunction(e2e3) + assert.False(t, set.minimized) access := set.Access() + assert.True(t, set.minimized) // Cannot express (E1 | E2), (E2 | E3) in an access/auth, // so the result is the conjunction of all entitlements + assert.False(t, set.IsMinimallyRepresentable()) expectedEntitlements := orderedmap.New[EntitlementOrderedSet](3) expectedEntitlements.Set(e1, struct{}{}) @@ -407,11 +600,14 @@ func TestEntitlementSet_Access(t *testing.T) { e2e3.Set(e3, struct{}{}) set.AddDisjunction(e2e3) + assert.False(t, set.minimized) access := set.Access() + assert.True(t, set.minimized) // Cannot express E1, (E2 | E3) in an access/auth, // so the result is the conjunction of all entitlements + assert.False(t, set.IsMinimallyRepresentable()) expectedEntitlements := orderedmap.New[EntitlementOrderedSet](3) expectedEntitlements.Set(e1, struct{}{}) @@ -444,12 +640,19 @@ func TestEntitlementSet_Access(t *testing.T) { e1e2.Set(e2, struct{}{}) set.AddDisjunction(e1e2) + assert.False(t, set.minimized) + + set.Minimize() + assert.True(t, set.minimized) set.Add(e1) + assert.False(t, set.minimized) access := set.Access() + assert.True(t, set.minimized) // NOTE: disjunction got removed during minimization + assert.True(t, set.IsMinimallyRepresentable()) expectedEntitlements := orderedmap.New[EntitlementOrderedSet](1) expectedEntitlements.Set(e1, struct{}{}) From 427a1af4a4e07bf7dee30a5fc97f00db86574c1e Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 May 2024 13:05:55 -0400 Subject: [PATCH 03/10] add check to validator for unrepresentable entitlements inference --- ..._to_v1_contract_upgrade_validation_test.go | 175 ++++++++++++++++++ ..._v0.42_to_v1_contract_upgrade_validator.go | 64 +++++++ 2 files changed, 239 insertions(+) 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 0ba7d36662..5a2f0ba892 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 @@ -19,6 +19,7 @@ package stdlib_test import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -196,6 +197,14 @@ func assertMissingDeclarationError(t *testing.T, err error, declName string) boo return assert.Equal(t, declName, missingDeclError.Name) } +func assertInvalidEntitlementsUpgradeError(t *testing.T, err error, declName string, accessString string) { + var invalidEntitlements *stdlib.UnrepresentableEntitlementsUpgrade + require.ErrorAs(t, err, &invalidEntitlements) + + require.Equal(t, declName, invalidEntitlements.Type.QualifiedString()) + require.Equal(t, accessString, invalidEntitlements.InvalidAuthorization.QualifiedString()) +} + func getContractUpdateError(t *testing.T, err error, contractName string) *stdlib.ContractUpdateError { require.Error(t, err) @@ -2613,3 +2622,169 @@ func TestEnumUpdates(t *testing.T) { require.NoError(t, err) }) } + +func TestContractUpgradeIsRepresentable(t *testing.T) { + + t.Parallel() + + test := func(isInterface bool) { + nameString := "composite" + if isInterface { + nameString = "interface" + } + + codeString := "" + if isInterface { + codeString = "interface" + } + + functionImplString := "{}" + if isInterface { + functionImplString = "" + } + + t.Run(fmt.Sprintf("grant one entitlement %s", nameString), func(t *testing.T) { + + t.Parallel() + + var oldCode = fmt.Sprintf(` + access(all) contract %[1]s Test { + access(all) resource %[1]s R { + access(all) fun a() %[2]s + } + } + `, codeString, functionImplString) + + var newCode = fmt.Sprintf(` + access(all) contract %[1]s Test { + access(all) entitlement E + access(all) resource %[1]s R { + access(E) fun a() %[2]s + } + } + `, codeString, functionImplString) + + err := testContractUpdate(t, oldCode, newCode) + require.NoError(t, err) + }) + + t.Run(fmt.Sprintf("grant two entitlements %s", nameString), func(t *testing.T) { + + t.Parallel() + + var oldCode = fmt.Sprintf(` + access(all) contract %[1]s Test { + access(all) resource %[1]s R { + access(all) fun a() %[2]s + access(all) fun b() %[2]s + } + } + `, codeString, functionImplString) + + var newCode = fmt.Sprintf(` + access(all) contract %[1]s Test { + access(all) entitlement E + access(all) entitlement F + access(all) resource %[1]s R { + access(E) fun a() %[2]s + access(F) fun b() %[2]s + } + } + `, codeString, functionImplString) + + err := testContractUpdate(t, oldCode, newCode) + require.NoError(t, err) + }) + + t.Run(fmt.Sprintf("redundant disjunction %s", nameString), func(t *testing.T) { + + t.Parallel() + + var oldCode = fmt.Sprintf(` + access(all) contract %[1]s Test { + access(all) resource %[1]s R { + access(all) fun a() %[2]s + access(all) fun b() %[2]s + } + } + `, codeString, functionImplString) + + var newCode = fmt.Sprintf(` + access(all) contract %[1]s Test { + access(all) entitlement E + access(all) entitlement F + access(all) resource %[1]s R { + access(E) fun a() %[2]s + access(E | F) fun b() %[2]s + } + } + `, codeString, functionImplString) + + err := testContractUpdate(t, oldCode, newCode) + require.NoError(t, err) + }) + + t.Run(fmt.Sprintf("non-redundant disjunction %s", nameString), func(t *testing.T) { + + t.Parallel() + + var oldCode = fmt.Sprintf(` + access(all) contract %[1]s Test { + access(all) resource %[1]s R { + access(all) fun a() %[2]s + access(all) fun b() %[2]s + } + } + `, codeString, functionImplString) + + var newCode = fmt.Sprintf(` + access(all) contract %[1]s Test { + access(all) entitlement E + access(all) entitlement F + access(all) entitlement G + access(all) resource %[1]s R { + access(E) fun a() %[2]s + access(F | G) fun b() %[2]s + } + } + `, codeString, functionImplString) + + err := testContractUpdate(t, oldCode, newCode) + cause := getSingleContractUpdateErrorCause(t, err, "Test") + assertInvalidEntitlementsUpgradeError(t, cause, "Test.R", "Test.E, Test.F, Test.G") + }) + + t.Run(fmt.Sprintf("two disjunctions %s", nameString), func(t *testing.T) { + + t.Parallel() + + var oldCode = fmt.Sprintf(` + access(all) contract %[1]s Test { + access(all) resource %[1]s R { + access(all) fun a() %[2]s + access(all) fun b() %[2]s + } + } + `, codeString, functionImplString) + + var newCode = fmt.Sprintf(` + access(all) contract %[1]s Test { + access(all) entitlement E + access(all) entitlement F + access(all) entitlement G + access(all) resource %[1]s R { + access(E | F) fun a() %[2]s + access(F | G) fun b() %[2]s + } + } + `, codeString, functionImplString) + + err := testContractUpdate(t, oldCode, newCode) + cause := getSingleContractUpdateErrorCause(t, err, "Test") + assertInvalidEntitlementsUpgradeError(t, cause, "Test.R", "Test.E, Test.F, Test.G") + }) + } + + test(true) + test(false) +} 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 2ff6e8a4f4..7cc5870309 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 @@ -114,6 +114,16 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) Validate() error { validator.checkConformanceV1, ) + // Check entitlements added to nested decls are all representable + nestedComposites := newRootDecl.DeclarationMembers().Composites() + for _, nestedComposite := range nestedComposites { + validator.validateEntitlementsRepresentableComposite(nestedComposite) + } + nestedInterfaces := newRootDecl.DeclarationMembers().Interfaces() + for _, nestedInterface := range nestedInterfaces { + validator.validateEntitlementsRepresentableInterface(nestedInterface) + } + if underlyingValidator.hasErrors() { return underlyingValidator.getContractUpdateError() } @@ -284,6 +294,34 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) expectedAuthorizationOf return intersectionType.SupportedEntitlements().Access() } +func (validator *CadenceV042ToV1ContractUpdateValidator) validateEntitlementsRepresentableComposite(decl *ast.CompositeDeclaration) { + dummyNominalType := ast.NewNominalType(nil, decl.Identifier, nil) + compositeType := validator.getCompositeType(dummyNominalType) + supportedEntitlements := compositeType.SupportedEntitlements() + + if !supportedEntitlements.IsMinimallyRepresentable() { + validator.report(&UnrepresentableEntitlementsUpgrade{ + Type: compositeType, + InvalidAuthorization: supportedEntitlements.Access(), + Range: decl.Range, + }) + } +} + +func (validator *CadenceV042ToV1ContractUpdateValidator) validateEntitlementsRepresentableInterface(decl *ast.InterfaceDeclaration) { + dummyNominalType := ast.NewNominalType(nil, decl.Identifier, nil) + interfaceType := validator.getInterfaceType(dummyNominalType) + supportedEntitlements := interfaceType.SupportedEntitlements() + + if !supportedEntitlements.IsMinimallyRepresentable() { + validator.report(&UnrepresentableEntitlementsUpgrade{ + Type: interfaceType, + InvalidAuthorization: supportedEntitlements.Access(), + Range: decl.Range, + }) + } +} + func (validator *CadenceV042ToV1ContractUpdateValidator) checkEntitlementsUpgrade(newType *ast.ReferenceType) error { newAuthorization := newType.Authorization newEntitlementSet, isEntitlementsSet := newAuthorization.(ast.EntitlementSet) @@ -853,3 +891,29 @@ func (e *AuthorizationMismatchError) Error() string { e.FoundAuthorization.QualifiedString(), ) } + +// UnrepresentableEntitlementsUpgrade is reported during a contract upgrade, +// when a composite or interface type is given access modifiers on its field that would +// cause the migration to produce an unrepresentable entitlement set for references to that type +type UnrepresentableEntitlementsUpgrade struct { + Type sema.Type + InvalidAuthorization sema.Access + ast.Range +} + +var _ errors.UserError = &UnrepresentableEntitlementsUpgrade{} +var _ errors.SecondaryError = &UnrepresentableEntitlementsUpgrade{} + +func (*UnrepresentableEntitlementsUpgrade) IsUserError() {} + +func (e *UnrepresentableEntitlementsUpgrade) Error() string { + return fmt.Sprintf( + "unsafe access modifiers on %s: the entitlements migration would grant references to this type %s authorization, which is too permissive.", + e.Type.QualifiedString(), + e.InvalidAuthorization.QualifiedString(), + ) +} + +func (e *UnrepresentableEntitlementsUpgrade) SecondaryError() string { + return "Consider removing any disjunction access modifiers" +} From 57b84eb2e8c99d2bb79a107578a9dd8a8aae36e9 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 May 2024 17:02:30 -0400 Subject: [PATCH 04/10] defer --- runtime/sema/entitlementset.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/sema/entitlementset.go b/runtime/sema/entitlementset.go index 7e36f1d646..964c86df4c 100644 --- a/runtime/sema/entitlementset.go +++ b/runtime/sema/entitlementset.go @@ -132,7 +132,7 @@ func (s *EntitlementSet) Merge(other *EntitlementSet) { // It removes disjunctions that contain entitlements // which are also in the entitlement set func (s *EntitlementSet) Minimize() { - s.minimized = true + defer func() { s.minimized = true }() // If there are no entitlements or no disjunctions, // there is nothing to minimize From 8133e7f0f2842f1474626eef85882efaab1112b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 1 May 2024 15:01:03 -0700 Subject: [PATCH 05/10] improve test names, add visual representation --- runtime/sema/entitlementset_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/runtime/sema/entitlementset_test.go b/runtime/sema/entitlementset_test.go index dab1054537..b8ee3d66c0 100644 --- a/runtime/sema/entitlementset_test.go +++ b/runtime/sema/entitlementset_test.go @@ -451,7 +451,7 @@ func TestEntitlementSet_MinimallyRepresentable(t *testing.T) { func TestEntitlementSet_Access(t *testing.T) { t.Parallel() - t.Run("no entitlements, no disjunctions", func(t *testing.T) { + t.Run("no entitlements, no disjunctions: {} = unauthorized", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} @@ -462,7 +462,7 @@ func TestEntitlementSet_Access(t *testing.T) { assert.Equal(t, UnauthorizedAccess, access) }) - t.Run("entitlements, no disjunctions", func(t *testing.T) { + t.Run("entitlements, no disjunctions: {E1, E2} = auth(E1, E2)", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} @@ -494,7 +494,7 @@ func TestEntitlementSet_Access(t *testing.T) { ) }) - t.Run("no entitlements, one disjunction", func(t *testing.T) { + t.Run("no entitlements, one disjunction: {(E1 | E2)} = auth(E1 | E2)", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} @@ -525,7 +525,7 @@ func TestEntitlementSet_Access(t *testing.T) { ) }) - t.Run("no entitlements, two disjunctions", func(t *testing.T) { + t.Run("no entitlements, two disjunctions: {(E1 | E2), (E2 | E3)} = auth(E1, E2, E3)", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} @@ -578,7 +578,7 @@ func TestEntitlementSet_Access(t *testing.T) { ) }) - t.Run("entitlement, one disjunction, minimal", func(t *testing.T) { + t.Run("entitlement, one disjunction, not minimal: {E1, (E2 | E3)} = auth(E1, E2, E3)", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} @@ -623,7 +623,7 @@ func TestEntitlementSet_Access(t *testing.T) { ) }) - t.Run("entitlement, one disjunction, not minimal", func(t *testing.T) { + t.Run("entitlement, one disjunction, minimal: {(E1 | E2), E1} = auth(E1)", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} From 5662eff0203ba943ef10d285565eb0978c037565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 1 May 2024 15:04:40 -0700 Subject: [PATCH 06/10] add visual representation of sets to test names --- runtime/sema/entitlementset_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/runtime/sema/entitlementset_test.go b/runtime/sema/entitlementset_test.go index b8ee3d66c0..19692a0ff9 100644 --- a/runtime/sema/entitlementset_test.go +++ b/runtime/sema/entitlementset_test.go @@ -286,14 +286,14 @@ func TestEntitlementSet_Minimize(t *testing.T) { func TestEntitlementSet_MinimallyRepresentable(t *testing.T) { t.Parallel() - t.Run("no entitlements, no disjunctions", func(t *testing.T) { + t.Run("no entitlements, no disjunctions: {} = true", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} assert.True(t, set.IsMinimallyRepresentable()) }) - t.Run("one entitlement, no disjunctions", func(t *testing.T) { + t.Run("one entitlement, no disjunctions: {E1} = true", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} @@ -306,7 +306,7 @@ func TestEntitlementSet_MinimallyRepresentable(t *testing.T) { assert.True(t, set.IsMinimallyRepresentable()) }) - t.Run("two entitlements, no disjunctions", func(t *testing.T) { + t.Run("two entitlements, no disjunctions: {E1, E2} = true", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} @@ -324,7 +324,7 @@ func TestEntitlementSet_MinimallyRepresentable(t *testing.T) { assert.True(t, set.IsMinimallyRepresentable()) }) - t.Run("one entitlement, redundant disjunction", func(t *testing.T) { + t.Run("one entitlement, redundant disjunction: {E1, (E1 | E2)} = true", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} @@ -348,7 +348,7 @@ func TestEntitlementSet_MinimallyRepresentable(t *testing.T) { assert.True(t, set.IsMinimallyRepresentable()) }) - t.Run("two entitlements, two redundant disjunctions", func(t *testing.T) { + t.Run("two entitlements, two redundant disjunctions: {E1, E3, (E1 | E2), (E3 | E4)} = true", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} @@ -387,7 +387,7 @@ func TestEntitlementSet_MinimallyRepresentable(t *testing.T) { assert.True(t, set.IsMinimallyRepresentable()) }) - t.Run("one entitlement, non-redundant disjunction", func(t *testing.T) { + t.Run("one entitlement, non-redundant disjunction: {E1, (E3 | E2)} = false", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} @@ -415,7 +415,7 @@ func TestEntitlementSet_MinimallyRepresentable(t *testing.T) { assert.False(t, set.IsMinimallyRepresentable()) }) - t.Run("two disjunctions", func(t *testing.T) { + t.Run("two disjunctions: {(E1 | E2), (E2 | E3)} = false", func(t *testing.T) { t.Parallel() set := &EntitlementSet{} From 4d586a269e44722745e102a691f73f0dc12a9064 Mon Sep 17 00:00:00 2001 From: turbolent Date: Wed, 1 May 2024 23:57:21 +0000 Subject: [PATCH 07/10] v1.0.0-preview.25 --- npm-packages/cadence-parser/package.json | 2 +- version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/npm-packages/cadence-parser/package.json b/npm-packages/cadence-parser/package.json index 99e8e349cd..90a6c5ee2e 100644 --- a/npm-packages/cadence-parser/package.json +++ b/npm-packages/cadence-parser/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/cadence-parser", - "version": "1.0.0-preview.24", + "version": "1.0.0-preview.25", "description": "The Cadence parser", "homepage": "https://github.com/onflow/cadence", "repository": { diff --git a/version.go b/version.go index 2f275547b5..ad7d3828cd 100644 --- a/version.go +++ b/version.go @@ -21,4 +21,4 @@ package cadence -const Version = "v1.0.0-preview.24" +const Version = "v1.0.0-preview.25" From d379376b51bfbc291d716eabeb6fa5a9a19e052e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 1 May 2024 17:14:16 -0700 Subject: [PATCH 08/10] update to tagged version of atree --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fbfad771c5..90b6832420 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/kr/pretty v0.3.1 github.com/leanovate/gopter v0.2.9 github.com/logrusorgru/aurora/v4 v4.0.0 - github.com/onflow/atree v0.6.1-0.20240429171214-e4400b25dfa9 + github.com/onflow/atree v0.8.0-rc.1 github.com/rivo/uniseg v0.4.4 github.com/schollz/progressbar/v3 v3.13.1 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index be06ade17a..9e9d79b54e 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvr github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/onflow/atree v0.6.1-0.20240429171214-e4400b25dfa9 h1:lS/47Nt8qRIEC+V8QPobTAWnudK982u/wJboucugndg= -github.com/onflow/atree v0.6.1-0.20240429171214-e4400b25dfa9/go.mod h1:7YNAyCd5JENq+NzH+fR1ABUZVzbSq9dkt0+5fZH3L2A= +github.com/onflow/atree v0.8.0-rc.1 h1:sTxguTcS0qq8vv0EcJri0OO2be8/GCZDARGm7Nt0XRg= +github.com/onflow/atree v0.8.0-rc.1/go.mod h1:7YNAyCd5JENq+NzH+fR1ABUZVzbSq9dkt0+5fZH3L2A= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= From 436918fc640ea6979683cbd6fe301545356bf1ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 1 May 2024 17:25:02 -0700 Subject: [PATCH 09/10] set expected version --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index ae0482d117..cf42ff0c06 100644 --- a/version.go +++ b/version.go @@ -21,4 +21,4 @@ package cadence -const Version = "v1.0.0-preview-atree-register-inlining.23" +const Version = "v1.0.0-preview-atree-register-inlining.24" From 153d4a33bdc5b4d747fa9eb62cdcbdebcf08dd74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 1 May 2024 17:25:31 -0700 Subject: [PATCH 10/10] go mod tidy --- tools/storage-explorer/go.mod | 2 +- tools/storage-explorer/go.sum | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/storage-explorer/go.mod b/tools/storage-explorer/go.mod index 9da1febc3d..f27e6acb24 100644 --- a/tools/storage-explorer/go.mod +++ b/tools/storage-explorer/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/gorilla/mux v1.8.1 - github.com/onflow/atree v0.6.1-0.20240429171214-e4400b25dfa9 + github.com/onflow/atree v0.8.0-rc.1 github.com/onflow/cadence v1.0.0-M8 github.com/onflow/flow-go v0.34.0-crescendo-preview.5.0.20240229164931-a67398875618 github.com/rs/zerolog v1.32.0 diff --git a/tools/storage-explorer/go.sum b/tools/storage-explorer/go.sum index 124422d510..dc74f5741e 100644 --- a/tools/storage-explorer/go.sum +++ b/tools/storage-explorer/go.sum @@ -1777,6 +1777,7 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6 github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f/go.mod h1:xvP61FoOs95K7IYdIYRnNcYQGf4nbF/uuJ0tHf4DRuM= github.com/onflow/atree v0.6.1-0.20240429171214-e4400b25dfa9 h1:lS/47Nt8qRIEC+V8QPobTAWnudK982u/wJboucugndg= github.com/onflow/atree v0.6.1-0.20240429171214-e4400b25dfa9/go.mod h1:7YNAyCd5JENq+NzH+fR1ABUZVzbSq9dkt0+5fZH3L2A= +github.com/onflow/atree v0.8.0-rc.1/go.mod h1:7YNAyCd5JENq+NzH+fR1ABUZVzbSq9dkt0+5fZH3L2A= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.2-0.20240227190927-0e6ce7e3222b h1:oXHQft30sElpK7G3xWB5tEizI2G+S4p64iVh0LtX4E0=