diff --git a/runtime/config.go b/runtime/config.go index d2c9d459dd..3e3a273ff1 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -39,4 +39,6 @@ type Config struct { AttachmentsEnabled bool // LegacyContractUpgradeEnabled enabled specifies whether to use the old parser when parsing an old contract LegacyContractUpgradeEnabled bool + // ContractUpdateTypeRemovalEnabled specifies if type removal is enabled in contract updates + ContractUpdateTypeRemovalEnabled bool } diff --git a/runtime/contract_update_validation_test.go b/runtime/contract_update_validation_test.go index 36e74c5260..24cee33329 100644 --- a/runtime/contract_update_validation_test.go +++ b/runtime/contract_update_validation_test.go @@ -82,10 +82,8 @@ func newContractRemovalTransaction(contractName string) string { ) } -func newContractDeploymentTransactor(t *testing.T, withC1Upgrade bool) func(code string) error { - config := DefaultTestInterpreterConfig - config.AttachmentsEnabled = true - config.LegacyContractUpgradeEnabled = withC1Upgrade +func newContractDeploymentTransactor(t *testing.T, config Config) func(code string) error { + rt := NewTestInterpreterRuntimeWithConfig(config) accountCodes := map[Location][]byte{} @@ -133,8 +131,8 @@ func newContractDeploymentTransactor(t *testing.T, withC1Upgrade bool) func(code // testDeployAndUpdate deploys a contract in one transaction, // then updates the contract in another transaction -func testDeployAndUpdate(t *testing.T, name string, oldCode string, newCode string, withC1Upgrade bool) error { - executeTransaction := newContractDeploymentTransactor(t, withC1Upgrade) +func testDeployAndUpdate(t *testing.T, name string, oldCode string, newCode string, config Config) error { + executeTransaction := newContractDeploymentTransactor(t, config) err := executeTransaction(newContractAddTransaction(name, oldCode)) require.NoError(t, err) @@ -143,36 +141,75 @@ func testDeployAndUpdate(t *testing.T, name string, oldCode string, newCode stri // testDeployAndRemove deploys a contract in one transaction, // then removes the contract in another transaction -func testDeployAndRemove(t *testing.T, name string, code string, withC1Upgrade bool) error { - executeTransaction := newContractDeploymentTransactor(t, withC1Upgrade) +func testDeployAndRemove(t *testing.T, name string, code string, config Config) error { + executeTransaction := newContractDeploymentTransactor(t, config) err := executeTransaction(newContractAddTransaction(name, code)) require.NoError(t, err) return executeTransaction(newContractRemovalTransaction(name)) } -func testWithValidators(t *testing.T, name string, testFunc func(t *testing.T, withC1Upgrade bool)) { +func testWithValidators(t *testing.T, name string, testFunc func(t *testing.T, config Config)) { for _, withC1Upgrade := range []bool{true, false} { withC1Upgrade := withC1Upgrade name := name if withC1Upgrade { - name = fmt.Sprintf("%s (with c1 validator)", name) + name = fmt.Sprintf("%s (with C1 validator)", name) } t.Run(name, func(t *testing.T) { t.Parallel() - testFunc(t, withC1Upgrade) + config := DefaultTestInterpreterConfig + config.LegacyContractUpgradeEnabled = withC1Upgrade + testFunc(t, config) }) } } +func testWithValidatorsAndTypeRemovalEnabled( + t *testing.T, + name string, + testFunc func(t *testing.T, config Config), +) { + for _, withC1Upgrade := range []bool{true, false} { + withC1Upgrade := withC1Upgrade + name := name + + for _, withTypeRemovalEnabled := range []bool{true, false} { + withTypeRemovalEnabled := withTypeRemovalEnabled + name := name + + switch { + case withC1Upgrade && withTypeRemovalEnabled: + name = fmt.Sprintf("%s (with C1 validator and type removal enabled)", name) + + case withC1Upgrade: + name = fmt.Sprintf("%s (with C1 validator)", name) + + case withTypeRemovalEnabled: + name = fmt.Sprintf("%s (with type removal enabled)", name) + } + + t.Run(name, func(t *testing.T) { + t.Parallel() + + config := DefaultTestInterpreterConfig + config.LegacyContractUpgradeEnabled = withC1Upgrade + config.ContractUpdateTypeRemovalEnabled = withTypeRemovalEnabled + + testFunc(t, config) + }) + } + } +} + func TestRuntimeContractUpdateValidation(t *testing.T) { t.Parallel() - testWithValidators(t, "change field type", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "change field type", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -192,14 +229,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertFieldTypeMismatchError(t, cause, "Test", "a", "String", "Int") }) - testWithValidators(t, "add field", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "add field", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -223,14 +260,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertExtraneousFieldError(t, cause, "Test", "b") }) - testWithValidators(t, "remove field", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "remove field", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -254,11 +291,11 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) require.NoError(t, err) }) - testWithValidators(t, "change nested decl field type", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "change nested decl field type", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -300,14 +337,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertFieldTypeMismatchError(t, cause, "TestResource", "b", "Int", "String") }) - testWithValidators(t, "add field to nested decl", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "add field to nested decl", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -351,14 +388,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertExtraneousFieldError(t, cause, "TestResource", "c") }) - testWithValidators(t, "change indirect field type", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "change indirect field type", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -402,14 +439,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertFieldTypeMismatchError(t, cause, "TestStruct", "b", "Int", "String") }) - testWithValidators(t, "circular types refs", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "circular types refs", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -477,14 +514,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertFieldTypeMismatchError(t, cause, "Bar", "d", "Bar?", "String") }) - testWithValidators(t, "qualified vs unqualified nominal type", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "qualified vs unqualified nominal type", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -528,11 +565,11 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) require.NoError(t, err) }) - testWithValidators(t, "change imported nominal type to local", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "change imported nominal type to local", func(t *testing.T, config Config) { const importCode = ` access(all) contract TestImport { @@ -549,7 +586,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - executeTransaction := newContractDeploymentTransactor(t, withC1Upgrade) + executeTransaction := newContractDeploymentTransactor(t, config) err := executeTransaction(newContractAddTransaction("TestImport", importCode)) require.NoError(t, err) @@ -598,7 +635,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { assertFieldTypeMismatchError(t, cause, "Test", "x", "TestImport.TestStruct", "TestStruct") }) - testWithValidators(t, "change imported field nominal type location", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "change imported field nominal type location", func(t *testing.T, config Config) { runtime := NewTestInterpreterRuntime() @@ -762,7 +799,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { assertFieldTypeMismatchError(t, cause, "Test", "x", "TestImport.TestStruct", "TestImport.TestStruct") }) - testWithValidators(t, "change imported non-field nominal type location", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "change imported non-field nominal type location", func(t *testing.T, config Config) { runtime := NewTestInterpreterRuntime() @@ -918,7 +955,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { require.NoError(t, err) }) - testWithValidators(t, "change imported field nominal type location implicitly", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "change imported field nominal type location implicitly", func(t *testing.T, config Config) { runtime := NewTestInterpreterRuntime() @@ -1105,7 +1142,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { assertFieldTypeMismatchError(t, cause, "Test", "x", "TestImport.TestStruct", "TestImport.TestStruct") }) - testWithValidators(t, "contract interface update", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "contract interface update", func(t *testing.T, config Config) { const oldCode = ` access(all) contract interface Test { @@ -1121,14 +1158,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertFieldTypeMismatchError(t, cause, "Test", "a", "String", "Int") }) - testWithValidators(t, "convert interface to contract", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "convert interface to contract", func(t *testing.T, config Config) { const oldCode = ` access(all) contract interface Test { @@ -1152,7 +1189,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") @@ -1165,7 +1202,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { ) }) - testWithValidators(t, "convert contract to interface", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "convert contract to interface", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1189,7 +1226,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") @@ -1202,7 +1239,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { ) }) - testWithValidators(t, "change non stored", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "change non stored", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1278,7 +1315,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) // Changing unused public composite types should also fail, since those could be // referred by anyone in the chain, and may cause data inconsistency. @@ -1288,7 +1325,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { assertFieldTypeMismatchError(t, cause, "UnusedStruct", "a", "Int", "String") }) - testWithValidators(t, "change enum type", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "change enum type", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1322,14 +1359,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertConformanceMismatchError(t, cause, "Foo", "UInt8") }) - testWithValidators(t, "change nested interface", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "change nested interface", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1363,14 +1400,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertFieldTypeMismatchError(t, cause, "TestStruct", "a", "String", "Int") }) - testWithValidators(t, "change nested interface to struct", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "change nested interface to struct", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1392,7 +1429,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") @@ -1405,7 +1442,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { ) }) - testWithValidators(t, "adding a nested struct", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "adding a nested struct", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1424,11 +1461,11 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) require.NoError(t, err) }) - testWithValidators(t, "removing a nested struct", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "removing a nested struct", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1447,14 +1484,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertMissingDeclarationError(t, cause, "TestStruct") }) - testWithValidators(t, "add and remove field", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "add and remove field", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1474,14 +1511,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertExtraneousFieldError(t, cause, "Test", "b") }) - testWithValidators(t, "multiple errors", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "multiple errors", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1517,7 +1554,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) updateErr := getContractUpdateError(t, err, "Test") @@ -1537,7 +1574,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { ) }) - testWithValidators(t, "check error messages", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "check error messages", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1573,7 +1610,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) const expectedError = "error: mismatching field `a` in `Test`\n" + @@ -1597,7 +1634,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { require.Contains(t, err.Error(), expectedError) }) - testWithValidators(t, "Test reference types", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Test reference types", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1637,11 +1674,11 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) require.NoError(t, err) }) - testWithValidators(t, "Test function type", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Test function type", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -1677,13 +1714,13 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) assert.Contains(t, err.Error(), "error: field add has non-storable type: fun(Int, Int): Int") }) - testWithValidators(t, "Test conformance", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Test conformance", func(t *testing.T, config Config) { const importCode = ` access(all) contract TestImport { @@ -1693,7 +1730,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - executeTransaction := newContractDeploymentTransactor(t, withC1Upgrade) + executeTransaction := newContractDeploymentTransactor(t, config) err := executeTransaction(newContractAddTransaction("TestImport", importCode)) require.NoError(t, err) @@ -1862,7 +1899,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, false) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, DefaultTestInterpreterConfig) require.NoError(t, err) }) @@ -1976,11 +2013,13 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, true) + config := DefaultTestInterpreterConfig + config.LegacyContractUpgradeEnabled = true + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) require.NoError(t, err) }) - testWithValidators(t, "Test intersection types", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Test intersection types", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2040,11 +2079,11 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) require.NoError(t, err) }) - testWithValidators(t, "Test invalid intersection types change", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Test invalid intersection types change", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2096,7 +2135,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) assert.Contains(t, err.Error(), "access(all) var a: {TestInterface}"+ @@ -2108,7 +2147,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { "incompatible type annotations. expected `{TestInterface}`, found `TestStruct`") }) - testWithValidators(t, "enum valid", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "enum valid", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2128,11 +2167,11 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) require.NoError(t, err) }) - testWithValidators(t, "enum remove case", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "enum remove case", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2151,14 +2190,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertMissingEnumCasesError(t, cause, "Foo", 2, 1) }) - testWithValidators(t, "enum add case", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "enum add case", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2179,11 +2218,11 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) require.NoError(t, err) }) - testWithValidators(t, "enum swap cases", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "enum swap cases", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2205,7 +2244,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) updateErr := getContractUpdateError(t, err, "Test") @@ -2219,7 +2258,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { assertEnumCaseMismatchError(t, childErrors[2], "left", "up") }) - testWithValidators(t, "Remove and add struct", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Remove and add struct", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2241,7 +2280,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - executeTransaction := newContractDeploymentTransactor(t, withC1Upgrade) + executeTransaction := newContractDeploymentTransactor(t, config) err := executeTransaction(newContractAddTransaction("Test", oldCode)) require.NoError(t, err) @@ -2272,7 +2311,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { assertFieldTypeMismatchError(t, cause, "TestStruct", "a", "Int", "String") }) - testWithValidators(t, "Rename struct", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Rename struct", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2304,14 +2343,14 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") assertMissingDeclarationError(t, cause, "TestStruct") }) - testWithValidators(t, "Remove contract with enum", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Remove contract with enum", func(t *testing.T, config Config) { const code = ` access(all) contract Test { @@ -2320,13 +2359,13 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndRemove(t, "Test", code, withC1Upgrade) + err := testDeployAndRemove(t, "Test", code, config) RequireError(t, err) assertContractRemovalError(t, err, "Test") }) - testWithValidators(t, "Remove contract without enum", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Remove contract without enum", func(t *testing.T, config Config) { const code = ` access(all) contract Test { @@ -2340,11 +2379,11 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndRemove(t, "Test", code, withC1Upgrade) + err := testDeployAndRemove(t, "Test", code, config) require.NoError(t, err) }) - testWithValidators(t, "removing multiple nested structs", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "removing multiple nested structs", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2362,7 +2401,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { for i := 0; i < 1000; i++ { - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) updateErr := getContractUpdateError(t, err, "Test") @@ -2377,7 +2416,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } }) - testWithValidators(t, "Remove event", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Remove event", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2392,7 +2431,7 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) require.NoError(t, err) }) } @@ -2510,7 +2549,7 @@ func TestRuntimeContractUpdateConformanceChanges(t *testing.T) { t.Parallel() - testWithValidators(t, "Adding conformance", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Adding conformance", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2547,11 +2586,11 @@ func TestRuntimeContractUpdateConformanceChanges(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) require.NoError(t, err) }) - testWithValidators(t, "Adding conformance with new fields", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Adding conformance with new fields", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2587,7 +2626,7 @@ func TestRuntimeContractUpdateConformanceChanges(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") @@ -2595,7 +2634,7 @@ func TestRuntimeContractUpdateConformanceChanges(t *testing.T) { assertExtraneousFieldError(t, cause, "Foo", "name") }) - testWithValidators(t, "Removing conformance, one", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Removing conformance, one", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2629,7 +2668,7 @@ func TestRuntimeContractUpdateConformanceChanges(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") @@ -2637,7 +2676,7 @@ func TestRuntimeContractUpdateConformanceChanges(t *testing.T) { assertConformanceMismatchError(t, cause, "Foo", "Bar") }) - testWithValidators(t, "Removing conformance, multiple", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Removing conformance, multiple", func(t *testing.T, config Config) { const oldCode = ` access(all) @@ -2687,7 +2726,7 @@ func TestRuntimeContractUpdateConformanceChanges(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) RequireError(t, err) cause := getSingleContractUpdateErrorCause(t, err, "Test") @@ -2695,7 +2734,7 @@ func TestRuntimeContractUpdateConformanceChanges(t *testing.T) { assertConformanceMismatchError(t, cause, "Foo", "Baz") }) - testWithValidators(t, "Change conformance order", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "Change conformance order", func(t *testing.T, config Config) { const oldCode = ` access(all) contract Test { @@ -2735,11 +2774,11 @@ func TestRuntimeContractUpdateConformanceChanges(t *testing.T) { } ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) require.NoError(t, err) }) - testWithValidators(t, "missing comma in parameter list of old contract", func(t *testing.T, withC1Upgrade bool) { + testWithValidators(t, "missing comma in parameter list of old contract", func(t *testing.T, config Config) { address := common.MustBytesToAddress([]byte{0x42}) @@ -3061,360 +3100,482 @@ func TestRuntimeContractUpdateProgramCaching(t *testing.T) { }) } -func TestPragmaUpdates(t *testing.T) { +func TestTypeRemovalPragmaUpdates(t *testing.T) { t.Parallel() - testWithValidators(t, "Remove pragma", func(t *testing.T, withC1Upgrade bool) { + testWithValidatorsAndTypeRemovalEnabled(t, + "Remove pragma", + func(t *testing.T, config Config) { - const oldCode = ` - access(all) contract Test { - #foo(bar) - #baz - } - ` + const oldCode = ` + access(all) contract Test { + #foo(bar) + #baz + } + ` - const newCode = ` - access(all) contract Test { - #baz - } - ` + const newCode = ` + access(all) contract Test { + #baz + } + ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - require.NoError(t, err) - }) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) + require.NoError(t, err) + }, + ) - testWithValidators(t, "Remove removedType pragma", func(t *testing.T, withC1Upgrade bool) { + testWithValidatorsAndTypeRemovalEnabled(t, + "Remove removedType pragma", + func(t *testing.T, config Config) { - const oldCode = ` - access(all) contract Test { - #removedType(bar) - #baz - } - ` + const oldCode = ` + access(all) contract Test { + #removedType(bar) + #baz + } + ` - const newCode = ` - access(all) contract Test { - #baz - } - ` + const newCode = ` + access(all) contract Test { + #baz + } + ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.TypeRemovalPragmaRemovalError - require.ErrorAs(t, err, &expectedErr) - }) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) - testWithValidators(t, "removedType pragma moved into subdeclaration", func(t *testing.T, withC1Upgrade bool) { + if config.ContractUpdateTypeRemovalEnabled { + var expectedErr *stdlib.TypeRemovalPragmaRemovalError + require.ErrorAs(t, err, &expectedErr) + } else { + require.NoError(t, err) + } + }, + ) - const oldCode = ` - access(all) contract Test { - #removedType(bar) - access(all) struct S { + testWithValidatorsAndTypeRemovalEnabled(t, + "removedType pragma moved into sub-declaration", + func(t *testing.T, config Config) { - } - } - ` + const oldCode = ` + access(all) contract Test { + #removedType(bar) + access(all) struct S { - const newCode = ` - access(all) contract Test { - access(all) struct S { - #removedType(bar) - } - } - ` + } + } + ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.TypeRemovalPragmaRemovalError - require.ErrorAs(t, err, &expectedErr) - }) + const newCode = ` + access(all) contract Test { + access(all) struct S { + #removedType(bar) + } + } + ` - testWithValidators(t, "reorder removedType pragmas", func(t *testing.T, withC1Upgrade bool) { + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) - const oldCode = ` - access(all) contract Test { - #removedType(bar) - #removedType(foo) - } - ` + if config.ContractUpdateTypeRemovalEnabled { + var expectedErr *stdlib.TypeRemovalPragmaRemovalError + require.ErrorAs(t, err, &expectedErr) + } else { + require.NoError(t, err) + } + }, + ) - const newCode = ` - access(all) contract Test { - #removedType(foo) - #removedType(bar) - } - ` + testWithValidatorsAndTypeRemovalEnabled(t, + "reorder removedType pragmas", + func(t *testing.T, config Config) { - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - require.NoError(t, err) - }) + const oldCode = ` + access(all) contract Test { + #removedType(bar) + #removedType(foo) + } + ` - testWithValidators(t, "malformed removedType pragma integer", func(t *testing.T, withC1Upgrade bool) { + const newCode = ` + access(all) contract Test { + #removedType(foo) + #removedType(bar) + } + ` - const oldCode = ` - access(all) contract Test { - #baz - } - ` + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) + require.NoError(t, err) + }, + ) - const newCode = ` - access(all) contract Test { - #removedType(3) - #baz - } - ` - - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.InvalidTypeRemovalPragmaError - require.ErrorAs(t, err, &expectedErr) - }) + testWithValidatorsAndTypeRemovalEnabled(t, + "malformed removedType pragma integer", + func(t *testing.T, config Config) { - testWithValidators(t, "malformed removedType qualified name", func(t *testing.T, withC1Upgrade bool) { + const oldCode = ` + access(all) contract Test { + #baz + } + ` - const oldCode = ` - access(all) contract Test { - #baz - } - ` + const newCode = ` + access(all) contract Test { + #removedType(3) + #baz + } + ` - const newCode = ` - access(all) contract Test { - #removedType(X.Y) - #baz - } - ` - - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.InvalidTypeRemovalPragmaError - require.ErrorAs(t, err, &expectedErr) - }) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) - testWithValidators(t, "removedType with zero args", func(t *testing.T, withC1Upgrade bool) { + if config.ContractUpdateTypeRemovalEnabled { + var expectedErr *stdlib.InvalidTypeRemovalPragmaError + require.ErrorAs(t, err, &expectedErr) + } else { + require.NoError(t, err) + } + }, + ) - const oldCode = ` - access(all) contract Test { - } - ` + testWithValidatorsAndTypeRemovalEnabled( + t, + "malformed removedType qualified name", + func(t *testing.T, config Config) { - const newCode = ` - access(all) contract Test { - #removedType() - } - ` + const oldCode = ` + access(all) contract Test { + #baz + } + ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.InvalidTypeRemovalPragmaError - require.ErrorAs(t, err, &expectedErr) - }) + const newCode = ` + access(all) contract Test { + #removedType(X.Y) + #baz + } + ` - testWithValidators(t, "removedType with two args", func(t *testing.T, withC1Upgrade bool) { + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) - const oldCode = ` - access(all) contract Test { - } - ` + if config.ContractUpdateTypeRemovalEnabled { + var expectedErr *stdlib.InvalidTypeRemovalPragmaError + require.ErrorAs(t, err, &expectedErr) + } else { + require.NoError(t, err) + } + }, + ) - const newCode = ` - access(all) contract Test { - #removedType(x, y) - } - ` + testWithValidatorsAndTypeRemovalEnabled(t, + "removedType with zero args", + func(t *testing.T, config Config) { - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.InvalidTypeRemovalPragmaError - require.ErrorAs(t, err, &expectedErr) - }) + const oldCode = ` + access(all) contract Test { + } + ` - testWithValidators(t, "#removedType allows type removal", func(t *testing.T, withC1Upgrade bool) { + const newCode = ` + access(all) contract Test { + #removedType() + } + ` - const oldCode = ` - access(all) contract Test { - access(all) resource R {} - } - ` + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) - const newCode = ` - access(all) contract Test { - #removedType(R) - } - ` + if config.ContractUpdateTypeRemovalEnabled { - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - require.NoError(t, err) - }) + var expectedErr *stdlib.InvalidTypeRemovalPragmaError + require.ErrorAs(t, err, &expectedErr) + } else { + require.NoError(t, err) + } + }, + ) - testWithValidators(t, "#removedType allows two type removals", func(t *testing.T, withC1Upgrade bool) { + testWithValidatorsAndTypeRemovalEnabled(t, + "removedType with two args", + func(t *testing.T, config Config) { - const oldCode = ` - access(all) contract Test { - access(all) resource R {} - access(all) struct S {} - } - ` + const oldCode = ` + access(all) contract Test { + } + ` - const newCode = ` - access(all) contract Test { - #removedType(R) - #removedType(S) - } - ` + const newCode = ` + access(all) contract Test { + #removedType(x, y) + } + ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - require.NoError(t, err) - }) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) - testWithValidators(t, "#removedType does not allow resource interface type removal", func(t *testing.T, withC1Upgrade bool) { + if config.ContractUpdateTypeRemovalEnabled { + var expectedErr *stdlib.InvalidTypeRemovalPragmaError + require.ErrorAs(t, err, &expectedErr) + } else { + require.NoError(t, err) + } + }, + ) - const oldCode = ` - access(all) contract Test { - access(all) resource interface R {} - } - ` + testWithValidatorsAndTypeRemovalEnabled(t, + "#removedType allows type removal", + func(t *testing.T, config Config) { - const newCode = ` - access(all) contract Test { - #removedType(R) - } - ` + const oldCode = ` + access(all) contract Test { + access(all) resource R {} + } + ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.MissingDeclarationError - require.ErrorAs(t, err, &expectedErr) - }) + const newCode = ` + access(all) contract Test { + #removedType(R) + } + ` - testWithValidators(t, "#removedType does not allow struct interface type removal", func(t *testing.T, withC1Upgrade bool) { + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) - const oldCode = ` - access(all) contract Test { - access(all) struct interface S {} - } - ` + if config.ContractUpdateTypeRemovalEnabled { + require.NoError(t, err) + } else { + var expectedErr *stdlib.MissingDeclarationError + require.ErrorAs(t, err, &expectedErr) + } + }, + ) - const newCode = ` - access(all) contract Test { - #removedType(S) - } - ` + testWithValidatorsAndTypeRemovalEnabled(t, + "#removedType allows two type removals", + func(t *testing.T, config Config) { - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.MissingDeclarationError - require.ErrorAs(t, err, &expectedErr) - }) + const oldCode = ` + access(all) contract Test { + access(all) resource R {} + access(all) struct S {} + } + ` - testWithValidators(t, "#removedType can be added", func(t *testing.T, withC1Upgrade bool) { + const newCode = ` + access(all) contract Test { + #removedType(R) + #removedType(S) + } + ` - const oldCode = ` - access(all) contract Test { - #removedType(I) - access(all) resource R {} - } - ` + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) - const newCode = ` - access(all) contract Test { - #removedType(R) - #removedType(I) - } - ` + if config.ContractUpdateTypeRemovalEnabled { + require.NoError(t, err) + } else { + var expectedErr *stdlib.MissingDeclarationError + require.ErrorAs(t, err, &expectedErr) + } + }, + ) - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - require.NoError(t, err) - }) + testWithValidatorsAndTypeRemovalEnabled(t, + "#removedType does not allow resource interface type removal", + func(t *testing.T, config Config) { - testWithValidators(t, "#removedType can be added without removing a type", func(t *testing.T, withC1Upgrade bool) { + const oldCode = ` + access(all) contract Test { + access(all) resource interface R {} + } + ` - const oldCode = ` - access(all) contract Test { - } - ` + const newCode = ` + access(all) contract Test { + #removedType(R) + } + ` - const newCode = ` - access(all) contract Test { - #removedType(X) - } - ` + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - require.NoError(t, err) - }) + var expectedErr *stdlib.MissingDeclarationError + require.ErrorAs(t, err, &expectedErr) + }, + ) - testWithValidators(t, "declarations cannot co-exist with removed type of the same name, composite", func(t *testing.T, withC1Upgrade bool) { + testWithValidatorsAndTypeRemovalEnabled(t, + "#removedType does not allow struct interface type removal", + func(t *testing.T, config Config) { - const oldCode = ` - access(all) contract Test { - access(all) resource R {} - } - ` + const oldCode = ` + access(all) contract Test { + access(all) struct interface S {} + } + ` - const newCode = ` - access(all) contract Test { - #removedType(R) - access(all) resource R {} - } - ` + const newCode = ` + access(all) contract Test { + #removedType(S) + } + ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.UseOfRemovedTypeError - require.ErrorAs(t, err, &expectedErr) - }) + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) - testWithValidators(t, "declarations cannot co-exist with removed type of the same name, interface", func(t *testing.T, withC1Upgrade bool) { + var expectedErr *stdlib.MissingDeclarationError + require.ErrorAs(t, err, &expectedErr) + }, + ) - const oldCode = ` - access(all) contract Test { - access(all) resource interface R {} - } - ` + testWithValidatorsAndTypeRemovalEnabled(t, + "#removedType can be added", + func(t *testing.T, config Config) { - const newCode = ` - access(all) contract Test { - #removedType(R) - access(all) resource interface R {} - } - ` + const oldCode = ` + access(all) contract Test { + #removedType(I) + access(all) resource R {} + } + ` - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.UseOfRemovedTypeError - require.ErrorAs(t, err, &expectedErr) - }) + const newCode = ` + access(all) contract Test { + #removedType(R) + #removedType(I) + } + ` - testWithValidators(t, "declarations cannot co-exist with removed type of the same name, attachment", func(t *testing.T, withC1Upgrade bool) { + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) - const oldCode = ` - access(all) contract Test { - access(all) attachment R for AnyResource {} - } - ` + if config.ContractUpdateTypeRemovalEnabled { + require.NoError(t, err) + } else { + var expectedErr *stdlib.MissingDeclarationError + require.ErrorAs(t, err, &expectedErr) + } + }, + ) - const newCode = ` - access(all) contract Test { - #removedType(R) - access(all) attachment R for AnyResource {} - } - ` + testWithValidatorsAndTypeRemovalEnabled(t, + "#removedType can be added without removing a type", + func(t *testing.T, config Config) { - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.UseOfRemovedTypeError - require.ErrorAs(t, err, &expectedErr) - }) + const oldCode = ` + access(all) contract Test { + } + ` - testWithValidators(t, "#removedType is only scoped to the current declaration, inner", func(t *testing.T, withC1Upgrade bool) { + const newCode = ` + access(all) contract Test { + #removedType(X) + } + ` - const oldCode = ` - access(all) contract Test { - access(all) resource R {} - access(all) struct S {} - } - ` + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) + require.NoError(t, err) + }, + ) - const newCode = ` - access(all) contract Test { - access(all) struct S { - #removedType(R) - } - } - ` + testWithValidatorsAndTypeRemovalEnabled(t, + "declarations cannot co-exist with removed type of the same name, composite", + func(t *testing.T, config Config) { - err := testDeployAndUpdate(t, "Test", oldCode, newCode, withC1Upgrade) - var expectedErr *stdlib.MissingDeclarationError - require.ErrorAs(t, err, &expectedErr) - }) + const oldCode = ` + access(all) contract Test { + access(all) resource R {} + } + ` + + const newCode = ` + access(all) contract Test { + #removedType(R) + access(all) resource R {} + } + ` + + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) + + if config.ContractUpdateTypeRemovalEnabled { + var expectedErr *stdlib.UseOfRemovedTypeError + require.ErrorAs(t, err, &expectedErr) + } else { + require.NoError(t, err) + } + }, + ) + + testWithValidatorsAndTypeRemovalEnabled(t, + "declarations cannot co-exist with removed type of the same name, interface", + func(t *testing.T, config Config) { + + const oldCode = ` + access(all) contract Test { + access(all) resource interface R {} + } + ` + + const newCode = ` + access(all) contract Test { + #removedType(R) + access(all) resource interface R {} + } + ` + + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) + + if config.ContractUpdateTypeRemovalEnabled { + var expectedErr *stdlib.UseOfRemovedTypeError + require.ErrorAs(t, err, &expectedErr) + } else { + require.NoError(t, err) + } + }, + ) + + testWithValidatorsAndTypeRemovalEnabled(t, + "declarations cannot co-exist with removed type of the same name, attachment", + func(t *testing.T, config Config) { + + const oldCode = ` + access(all) contract Test { + access(all) attachment R for AnyResource {} + } + ` + + const newCode = ` + access(all) contract Test { + #removedType(R) + access(all) attachment R for AnyResource {} + } + ` + + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) + + if config.ContractUpdateTypeRemovalEnabled { + var expectedErr *stdlib.UseOfRemovedTypeError + require.ErrorAs(t, err, &expectedErr) + } else { + require.NoError(t, err) + } + }, + ) + + testWithValidatorsAndTypeRemovalEnabled(t, + "#removedType is only scoped to the current declaration, inner", + func(t *testing.T, config Config) { + + const oldCode = ` + access(all) contract Test { + access(all) resource R {} + access(all) struct S {} + } + ` + + const newCode = ` + access(all) contract Test { + access(all) struct S { + #removedType(R) + } + } + ` + + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) + + var expectedErr *stdlib.MissingDeclarationError + require.ErrorAs(t, err, &expectedErr) + }, + ) } diff --git a/runtime/environment.go b/runtime/environment.go index f0bec5bece..d38f8f76e1 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -184,15 +184,16 @@ func (e *interpreterEnvironment) newInterpreterConfig() *interpreter.Config { // and disable storage validation after each value modification. // Instead, storage is validated after commits (if validation is enabled), // see interpreterEnvironment.CommitStorage - AtreeStorageValidationEnabled: false, - Debugger: e.config.Debugger, - OnStatement: e.newOnStatementHandler(), - OnMeterComputation: e.newOnMeterComputation(), - OnFunctionInvocation: e.newOnFunctionInvocationHandler(), - OnInvokedFunctionReturn: e.newOnInvokedFunctionReturnHandler(), - CapabilityBorrowHandler: stdlib.BorrowCapabilityController, - CapabilityCheckHandler: stdlib.CheckCapabilityController, - LegacyContractUpgradeEnabled: e.config.LegacyContractUpgradeEnabled, + AtreeStorageValidationEnabled: false, + Debugger: e.config.Debugger, + OnStatement: e.newOnStatementHandler(), + OnMeterComputation: e.newOnMeterComputation(), + OnFunctionInvocation: e.newOnFunctionInvocationHandler(), + OnInvokedFunctionReturn: e.newOnInvokedFunctionReturnHandler(), + CapabilityBorrowHandler: stdlib.BorrowCapabilityController, + CapabilityCheckHandler: stdlib.CheckCapabilityController, + LegacyContractUpgradeEnabled: e.config.LegacyContractUpgradeEnabled, + ContractUpdateTypeRemovalEnabled: e.config.ContractUpdateTypeRemovalEnabled, } } diff --git a/runtime/interpreter/config.go b/runtime/interpreter/config.go index 8ad7626832..f9d322bd7f 100644 --- a/runtime/interpreter/config.go +++ b/runtime/interpreter/config.go @@ -72,4 +72,6 @@ type Config struct { CapabilityBorrowHandler CapabilityBorrowHandlerFunc // LegacyContractUpgradeEnabled specifies whether to fall back to the old parser when attempting a contract upgrade LegacyContractUpgradeEnabled bool + // ContractUpdateTypeRemovalEnabled specifies if type removal is enabled in contract updates + ContractUpdateTypeRemovalEnabled bool } diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index f240eb9f0a..59dd801f6e 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -1639,6 +1639,7 @@ func changeAccountContracts( memoryGauge := invocation.Interpreter.SharedState.Config.MemoryGauge legacyUpgradeEnabled := invocation.Interpreter.SharedState.Config.LegacyContractUpgradeEnabled + contractUpdateTypeRemovalEnabled := invocation.Interpreter.SharedState.Config.ContractUpdateTypeRemovalEnabled var oldProgram *ast.Program @@ -1684,6 +1685,9 @@ func changeAccountContracts( program.Program, ) } + + validator = validator.WithTypeRemovalEnabled(contractUpdateTypeRemovalEnabled) + err = validator.Validate() handleContractUpdateError(err) } 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 d0184702c0..ee5706967b 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 @@ -71,6 +71,10 @@ func NewCadenceV042ToV1ContractUpdateValidator( var _ UpdateValidator = &CadenceV042ToV1ContractUpdateValidator{} +func (validator *CadenceV042ToV1ContractUpdateValidator) isTypeRemovalEnabled() bool { + return validator.underlyingUpdateValidator.isTypeRemovalEnabled() +} + func (validator *CadenceV042ToV1ContractUpdateValidator) WithUserDefinedTypeChangeChecker( typeChangeCheckFunc func(oldTypeID common.TypeID, newTypeID common.TypeID) (checked, valid bool), ) *CadenceV042ToV1ContractUpdateValidator { @@ -78,6 +82,13 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) WithUserDefinedTypeChan return validator } +func (validator *CadenceV042ToV1ContractUpdateValidator) WithTypeRemovalEnabled( + enabled bool, +) UpdateValidator { + validator.underlyingUpdateValidator.WithTypeRemovalEnabled(enabled) + return validator +} + func (validator *CadenceV042ToV1ContractUpdateValidator) getCurrentDeclaration() ast.Declaration { return validator.underlyingUpdateValidator.getCurrentDeclaration() } diff --git a/runtime/stdlib/contract_update_validation.go b/runtime/stdlib/contract_update_validation.go index 33af83e863..cce723d63a 100644 --- a/runtime/stdlib/contract_update_validation.go +++ b/runtime/stdlib/contract_update_validation.go @@ -50,6 +50,9 @@ type UpdateValidator interface { oldDeclaration ast.Declaration, newDeclaration ast.Declaration, ) bool + + isTypeRemovalEnabled() bool + WithTypeRemovalEnabled(enabled bool) UpdateValidator } type checkConformanceFunc func( @@ -68,6 +71,7 @@ type ContractUpdateValidator struct { importLocations map[ast.Identifier]common.Location accountContractNamesProvider AccountContractNamesProvider errors []error + typeRemovalEnabled bool } // ContractUpdateValidator should implement ast.TypeEqualityChecker @@ -95,6 +99,15 @@ func NewContractUpdateValidator( } } +func (validator *ContractUpdateValidator) isTypeRemovalEnabled() bool { + return validator.typeRemovalEnabled +} + +func (validator *ContractUpdateValidator) WithTypeRemovalEnabled(enabled bool) UpdateValidator { + validator.typeRemovalEnabled = enabled + return validator +} + func (validator *ContractUpdateValidator) getCurrentDeclaration() ast.Declaration { return validator.currentDecl } @@ -354,10 +367,12 @@ func (validator *ContractUpdateValidator) checkNestedDeclarationRemoval( return } - // OK to remove a type if it is included in a #removedType pragma, and it is not an interface - if removedTypes.Contains(nestedDeclaration.DeclarationIdentifier().Identifier) && - !declarationKind.IsInterfaceDeclaration() { - return + if validator.typeRemovalEnabled { + // OK to remove a type if it is included in a #removedType pragma, and it is not an interface + if removedTypes.Contains(nestedDeclaration.DeclarationIdentifier().Identifier) && + !declarationKind.IsInterfaceDeclaration() { + return + } } validator.report(&MissingDeclarationError{ @@ -383,6 +398,10 @@ func checkTypeNotRemoved( newDeclaration ast.Declaration, removedTypes *orderedmap.OrderedMap[string, struct{}], ) { + if !validator.isTypeRemovalEnabled() { + return + } + if removedTypes.Contains(newDeclaration.DeclarationIdentifier().Identifier) { validator.report(&UseOfRemovedTypeError{ Declaration: newDeclaration, @@ -398,19 +417,22 @@ func checkNestedDeclarations( checkConformance checkConformanceFunc, ) { - // process pragmas first, as they determine whether types can later be removed - oldRemovedTypes := collectRemovedTypePragmas(validator, oldDeclaration.DeclarationMembers().Pragmas()) - removedTypes := collectRemovedTypePragmas(validator, newDeclaration.DeclarationMembers().Pragmas()) - - // #typeRemoval pragmas cannot be removed, so any that appear in the old program must appear in the new program - // they can however, be added, so use the new program's type removals for the purposes of checking the upgrade - oldRemovedTypes.Foreach(func(oldRemovedType string, _ struct{}) { - if !removedTypes.Contains(oldRemovedType) { - validator.report(&TypeRemovalPragmaRemovalError{ - RemovedType: oldRemovedType, - }) - } - }) + var removedTypes *orderedmap.OrderedMap[string, struct{}] + if validator.isTypeRemovalEnabled() { + // process pragmas first, as they determine whether types can later be removed + oldRemovedTypes := collectRemovedTypePragmas(validator, oldDeclaration.DeclarationMembers().Pragmas()) + removedTypes = collectRemovedTypePragmas(validator, newDeclaration.DeclarationMembers().Pragmas()) + + // #typeRemoval pragmas cannot be removed, so any that appear in the old program must appear in the new program + // they can however, be added, so use the new program's type removals for the purposes of checking the upgrade + oldRemovedTypes.Foreach(func(oldRemovedType string, _ struct{}) { + if !removedTypes.Contains(oldRemovedType) { + validator.report(&TypeRemovalPragmaRemovalError{ + RemovedType: oldRemovedType, + }) + } + }) + } oldNominalTypeDecls := getNestedNominalTypeDecls(oldDeclaration) diff --git a/runtime/tests/runtime_utils/testruntime.go b/runtime/tests/runtime_utils/testruntime.go index 3834335200..5ea11f137d 100644 --- a/runtime/tests/runtime_utils/testruntime.go +++ b/runtime/tests/runtime_utils/testruntime.go @@ -38,6 +38,7 @@ func NewTestInterpreterRuntimeWithConfig(config runtime.Config) TestInterpreterR var DefaultTestInterpreterConfig = runtime.Config{ AtreeValidationEnabled: true, + AttachmentsEnabled: true, } func NewTestInterpreterRuntime() TestInterpreterRuntime {