From 0d67ab9332d1f3df96eba78217a797a13186a8f7 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 4 Jun 2024 11:33:57 -0400 Subject: [PATCH 1/5] add flip --- cadence/20240604-cadence-type-removal.md | 93 ++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 cadence/20240604-cadence-type-removal.md diff --git a/cadence/20240604-cadence-type-removal.md b/cadence/20240604-cadence-type-removal.md new file mode 100644 index 00000000..3c7f81dc --- /dev/null +++ b/cadence/20240604-cadence-type-removal.md @@ -0,0 +1,93 @@ +--- +status: draft +flip: 275 +authors: Daniel Sainati (daniel.sainati@flowfoundation.org) +sponsor: Daniel Sainati (daniel.sainati@flowfoundation.org) +updated: 2024-06-04 +--- + +# FLIP 275: Removal of Types in Contract Updates + +## Objective + +This adds a new pragma to Cadence that when processed by the contract update validator will +allow a type to be removed from a contract, and also prevent a type with the same name from being +added to that contract later. + +## Motivation + +As originally outlined in https://github.com/onflow/cadence/issues/3210, users have been asking for +the ability to remove types from their contracts as they become outdated or otherwise unnecessary. +The Cadence 1.0 upgrade in particular has exacerbated this need, as the changes to access control have +rendered a number of types obselete. Additionally as all contracts on chain need to be updated for Crescendo, +the ability to remove dependencies on contracts that are difficult or otherwise challenging to update has become more important. + +## User Benefit + +This will allow users to remove deprecated or unnecessary type definitions from their contracts. In particular, +it unblocks the upgrade of the `FiatToken` contract on chain, a contract that numerous others depend upon. + +## Design Proposal + +As implemented in https://github.com/onflow/cadence/pull/3376 and https://github.com/onflow/cadence/pull/3380, this feature introduces a new +`#removedType` pragma to Cadence. This pragma takes a single argument, an identifier signifying the type to be removed. +Importantly, this identifier is not qualified, i.e. to remove a type `R` from a contract `C`, `#removedType(R)` would be used, rather than +`#removedType(C.R)`. This is because the pragma is added at the same scope at which the type to be removed was originally defined. +So, for example, to remove a resource `R` from `C` defined like so: + +```cadence +access(all) contract C { + + access(all) resource R { + // ... resource body + } + + // ... contract body +} +``` + +the pragma would be placed here: + +```cadence +access(all) contract C { + + #removedType(R) + + // ... contract body +} +``` + +Placing the pragma at the top level, or nested inside another definition in `C` has no effect, and does not permit the removal of `R`. + +When used correctly, the pragma is processed by the contract update validator and allows an update to `C` to be performed that does not include +a definition of the resource type `R`, even though the old version of `C` did include `R`. In fact, when the `#removedType(T)` pragma is present, +the contract update validator will reject any update to the contract that includes a definition of `T` at the same scope level as the pragma. This is +to prevent the type from being added back later with incompatible changes that would otherwise circumvent the contract update validation restrictions. + +Additionally, once present in a contract, the `#removedType` pragma may never be removed, as this would allow the type it removed to be re-added, +once again potentially circumventing the update validation. + +Lastly, the `#removedType` pragma may only be used with concrete types (`struct`s, `resource`s, `enum`s, and `attachment`s), not with interfaces. +This is due to the fact that a removed interface cannot be removed from any existing conformance lists in which it is present, and thus removing an +interface would irrevocably break any downstream types that inherited from it. + +### Drawbacks + +This does increase the ease with which users can break downstream code; removing a type definition will break +all code that uses that type until uses of the type are removed. In some cases this is more feasible than in others; +if a resource definition `R` is removed, removing a field of type `R` from a downstream contract is much easier than, say, +fixing an attachment `A` that is designed to be used with `R`. In the latter case, the attachment `A` is likely unusable and will also have to be removed. + +Additionally, any existing stored values of the removed type will be broken and un-interactable forever. +Because we do not currently possess a way to remove broken values from storage, the inability to load these values also means they will +sit in storage forever. This change significantly increases the priority of implementing a solution for deleting broken values in storage. + +### Alternatives Considered + +As outlined in https://github.com/onflow/cadence/issues/3210, an alternative pragma specificing a type replacement, +rather than a removal was considered. We elected to do the first, easier solution due to time constraints, but are open +to considering the second given sufficiently good reason. + +## Questions and Discussion + +* Is it worth enabling the removal of interface types as well as regular composites? From 67b8030e4754681cc1975c508d8886e92701b777 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 22 Oct 2024 12:46:25 -0700 Subject: [PATCH 2/5] Update the FLIP to match current state of things --- cadence/20240604-cadence-type-removal.md | 63 ++++++++++++++---------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/cadence/20240604-cadence-type-removal.md b/cadence/20240604-cadence-type-removal.md index 3c7f81dc..d3eb6680 100644 --- a/cadence/20240604-cadence-type-removal.md +++ b/cadence/20240604-cadence-type-removal.md @@ -1,9 +1,9 @@ --- -status: draft +status: proposed flip: 275 authors: Daniel Sainati (daniel.sainati@flowfoundation.org) sponsor: Daniel Sainati (daniel.sainati@flowfoundation.org) -updated: 2024-06-04 +updated: 2024-10-22 --- # FLIP 275: Removal of Types in Contract Updates @@ -18,21 +18,20 @@ added to that contract later. As originally outlined in https://github.com/onflow/cadence/issues/3210, users have been asking for the ability to remove types from their contracts as they become outdated or otherwise unnecessary. -The Cadence 1.0 upgrade in particular has exacerbated this need, as the changes to access control have -rendered a number of types obselete. Additionally as all contracts on chain need to be updated for Crescendo, -the ability to remove dependencies on contracts that are difficult or otherwise challenging to update has become more important. +The Cadence 1.0 upgrade in particular had exacerbated this need, as the changes to access control have +rendered a number of types obsolete. ## User Benefit -This will allow users to remove deprecated or unnecessary type definitions from their contracts. In particular, -it unblocks the upgrade of the `FiatToken` contract on chain, a contract that numerous others depend upon. +This will allow users to remove deprecated or unnecessary type definitions from their contracts. ## Design Proposal -As implemented in https://github.com/onflow/cadence/pull/3376 and https://github.com/onflow/cadence/pull/3380, this feature introduces a new -`#removedType` pragma to Cadence. This pragma takes a single argument, an identifier signifying the type to be removed. -Importantly, this identifier is not qualified, i.e. to remove a type `R` from a contract `C`, `#removedType(R)` would be used, rather than -`#removedType(C.R)`. This is because the pragma is added at the same scope at which the type to be removed was originally defined. +The proposal is to introduces a new `#removedType` pragma to Cadence. +This pragma takes a single argument, an identifier specifying the type that got removed. +Importantly, this identifier is not qualified, i.e. to remove a type `R` from a contract `C`, `#removedType(R)` +would be used, rather than `#removedType(C.R)`. +This is because the pragma is added at the same scope at which the type to be removed was originally defined. So, for example, to remove a resource `R` from `C` defined like so: ```cadence @@ -59,32 +58,42 @@ access(all) contract C { Placing the pragma at the top level, or nested inside another definition in `C` has no effect, and does not permit the removal of `R`. -When used correctly, the pragma is processed by the contract update validator and allows an update to `C` to be performed that does not include -a definition of the resource type `R`, even though the old version of `C` did include `R`. In fact, when the `#removedType(T)` pragma is present, -the contract update validator will reject any update to the contract that includes a definition of `T` at the same scope level as the pragma. This is -to prevent the type from being added back later with incompatible changes that would otherwise circumvent the contract update validation restrictions. +When used correctly, the pragma is processed by the contract update validator and allows an update to `C` to be +performed that does not include a definition of the resource type `R`, even though the old version of `C` did include `R`. +In fact, when the `#removedType(T)` pragma is present, the contract update validator will reject any update to the +contract that includes a definition of `T` at the same scope level as the pragma. +This is to prevent the type from being added back later with incompatible changes that would otherwise circumvent the +contract update validation restrictions. -Additionally, once present in a contract, the `#removedType` pragma may never be removed, as this would allow the type it removed to be re-added, -once again potentially circumventing the update validation. +Additionally, once present in a contract, the `#removedType(T)` pragma may never be removed, as this would allow the type +it removed to be re-added, once again potentially circumventing the update validation. -Lastly, the `#removedType` pragma may only be used with concrete types (`struct`s, `resource`s, `enum`s, and `attachment`s), not with interfaces. -This is due to the fact that a removed interface cannot be removed from any existing conformance lists in which it is present, and thus removing an -interface would irrevocably break any downstream types that inherited from it. +Lastly, the `#removedType` pragma may only be used with concrete types (`struct`s, `resource`s, `enum`s, and `attachment`s), +not with interfaces. +This is due to the fact that a removed interface cannot be removed from any existing conformance lists in which it is present, +and thus removing an interface would irrevocably break any downstream types that inherited from it. + +### Draft Implementation + +A prototype implementation was added in https://github.com/onflow/cadence/pull/3376 and https://github.com/onflow/cadence/pull/3380. +This is currently disabled by default, and has been added only as a proof of concept. ### Drawbacks This does increase the ease with which users can break downstream code; removing a type definition will break -all code that uses that type until uses of the type are removed. In some cases this is more feasible than in others; -if a resource definition `R` is removed, removing a field of type `R` from a downstream contract is much easier than, say, -fixing an attachment `A` that is designed to be used with `R`. In the latter case, the attachment `A` is likely unusable and will also have to be removed. +all code that uses that type until uses of the type are removed. +In some cases this is more feasible than in others; if a resource definition `R` is removed, removing a field of type +`R` from a downstream contract is much easier than, say, fixing an attachment `A` that is designed to be used with `R`. +In the latter case, the attachment `A` is likely unusable and will also have to be removed. -Additionally, any existing stored values of the removed type will be broken and un-interactable forever. -Because we do not currently possess a way to remove broken values from storage, the inability to load these values also means they will -sit in storage forever. This change significantly increases the priority of implementing a solution for deleting broken values in storage. +Additionally, any existing stored values of the removed type will be broken and un-usable forever. +Because we do not currently possess a way to remove broken values from storage, the inability to load these values also +means they will sit in storage forever. +This change significantly increases the priority of implementing a solution for deleting broken values in storage. ### Alternatives Considered -As outlined in https://github.com/onflow/cadence/issues/3210, an alternative pragma specificing a type replacement, +As outlined in https://github.com/onflow/cadence/issues/3210, an alternative pragma specifying a type replacement, rather than a removal was considered. We elected to do the first, easier solution due to time constraints, but are open to considering the second given sufficiently good reason. From 7b910f04a13f6016d541e4c1633d4d1822c8b12c Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 22 Oct 2024 12:46:25 -0700 Subject: [PATCH 3/5] Update the FLIP to match current state of things --- cadence/20240604-cadence-type-removal.md | 69 ++++++++++++++---------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/cadence/20240604-cadence-type-removal.md b/cadence/20240604-cadence-type-removal.md index 3c7f81dc..824faac9 100644 --- a/cadence/20240604-cadence-type-removal.md +++ b/cadence/20240604-cadence-type-removal.md @@ -1,9 +1,9 @@ --- -status: draft +status: proposed flip: 275 authors: Daniel Sainati (daniel.sainati@flowfoundation.org) sponsor: Daniel Sainati (daniel.sainati@flowfoundation.org) -updated: 2024-06-04 +updated: 2024-10-22 --- # FLIP 275: Removal of Types in Contract Updates @@ -18,21 +18,20 @@ added to that contract later. As originally outlined in https://github.com/onflow/cadence/issues/3210, users have been asking for the ability to remove types from their contracts as they become outdated or otherwise unnecessary. -The Cadence 1.0 upgrade in particular has exacerbated this need, as the changes to access control have -rendered a number of types obselete. Additionally as all contracts on chain need to be updated for Crescendo, -the ability to remove dependencies on contracts that are difficult or otherwise challenging to update has become more important. +The Cadence 1.0 upgrade in particular had exacerbated this need, as the changes to access control have +rendered a number of types obsolete. ## User Benefit -This will allow users to remove deprecated or unnecessary type definitions from their contracts. In particular, -it unblocks the upgrade of the `FiatToken` contract on chain, a contract that numerous others depend upon. +This will allow users to remove deprecated or unnecessary type definitions from their contracts. ## Design Proposal -As implemented in https://github.com/onflow/cadence/pull/3376 and https://github.com/onflow/cadence/pull/3380, this feature introduces a new -`#removedType` pragma to Cadence. This pragma takes a single argument, an identifier signifying the type to be removed. -Importantly, this identifier is not qualified, i.e. to remove a type `R` from a contract `C`, `#removedType(R)` would be used, rather than -`#removedType(C.R)`. This is because the pragma is added at the same scope at which the type to be removed was originally defined. +The proposal is to introduces a new `#removedType` pragma to Cadence. +This pragma takes a single argument, an identifier specifying the type that got removed. +Importantly, this identifier is not qualified, i.e. to remove a type `R` from a contract `C`, `#removedType(R)` +would be used, rather than `#removedType(C.R)`. +This is because the pragma is added at the same scope at which the type to be removed was originally defined. So, for example, to remove a resource `R` from `C` defined like so: ```cadence @@ -59,32 +58,46 @@ access(all) contract C { Placing the pragma at the top level, or nested inside another definition in `C` has no effect, and does not permit the removal of `R`. -When used correctly, the pragma is processed by the contract update validator and allows an update to `C` to be performed that does not include -a definition of the resource type `R`, even though the old version of `C` did include `R`. In fact, when the `#removedType(T)` pragma is present, -the contract update validator will reject any update to the contract that includes a definition of `T` at the same scope level as the pragma. This is -to prevent the type from being added back later with incompatible changes that would otherwise circumvent the contract update validation restrictions. +When used correctly, the pragma is processed by the contract update validator and allows an update to `C` to be +performed that does not include a definition of the resource type `R`, even though the old version of `C` did include `R`. +In fact, when the `#removedType(T)` pragma is present, the contract update validator will reject any update to the +contract that includes a definition of `T` at the same scope level as the pragma. +This is to prevent the type from being added back later with incompatible changes that would otherwise circumvent the +contract update validation restrictions. -Additionally, once present in a contract, the `#removedType` pragma may never be removed, as this would allow the type it removed to be re-added, -once again potentially circumventing the update validation. +Additionally, once present in a contract, the `#removedType(T)` pragma may never be removed, as this would allow the type +it removed to be re-added, once again potentially circumventing the update validation. -Lastly, the `#removedType` pragma may only be used with concrete types (`struct`s, `resource`s, `enum`s, and `attachment`s), not with interfaces. -This is due to the fact that a removed interface cannot be removed from any existing conformance lists in which it is present, and thus removing an -interface would irrevocably break any downstream types that inherited from it. +Lastly, the `#removedType` pragma may only be used with concrete types (`struct`s, `resource`s, `enum`s, and `attachment`s), +not with interfaces. +This is due to the fact that a removed interface cannot be removed from any existing conformance lists in which it is present, +and thus removing an interface would irrevocably break any downstream types that inherited from it. + +### Draft Implementation + +A prototype implementation was added in https://github.com/onflow/cadence/pull/3376 and https://github.com/onflow/cadence/pull/3380. +This is currently disabled by default, and has been added only as a proof of concept. ### Drawbacks -This does increase the ease with which users can break downstream code; removing a type definition will break -all code that uses that type until uses of the type are removed. In some cases this is more feasible than in others; -if a resource definition `R` is removed, removing a field of type `R` from a downstream contract is much easier than, say, -fixing an attachment `A` that is designed to be used with `R`. In the latter case, the attachment `A` is likely unusable and will also have to be removed. +Removing a type definition will break any downstream code that uses that type until uses of the type are removed. +In some cases this is more feasible than in others; if a resource definition `R` is removed, removing a field of type +`R` from a downstream contract is much easier than, say, fixing an attachment `A` that is designed to be used with `R`. +In the latter case, the attachment `A` is likely unusable and will also have to be removed. + +However, it is worth mentioning that this is no different from someone removing a function definition from a type, +and any downstream code that depends on that particular function being broken, which is already possible. +As such, the backward compatibility of deployed code is not guaranteed by (and is not an objective of) the contract +update validation anyway. -Additionally, any existing stored values of the removed type will be broken and un-interactable forever. -Because we do not currently possess a way to remove broken values from storage, the inability to load these values also means they will -sit in storage forever. This change significantly increases the priority of implementing a solution for deleting broken values in storage. +Additionally, any existing stored values of the removed type will be broken and un-usable forever. +Because we do not currently possess a way to remove broken values from storage, the inability to load these values also +means they will sit in storage forever. +This change significantly increases the priority of implementing a solution for deleting broken values in storage. ### Alternatives Considered -As outlined in https://github.com/onflow/cadence/issues/3210, an alternative pragma specificing a type replacement, +As outlined in https://github.com/onflow/cadence/issues/3210, an alternative pragma specifying a type replacement, rather than a removal was considered. We elected to do the first, easier solution due to time constraints, but are open to considering the second given sufficiently good reason. From 174075a64ddc68252d9a2f6357989ea3ffd4cc7b Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 22 Oct 2024 13:27:59 -0700 Subject: [PATCH 4/5] Add explanation on why type removal was not allowed so far --- cadence/20240604-cadence-type-removal.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cadence/20240604-cadence-type-removal.md b/cadence/20240604-cadence-type-removal.md index 824faac9..9a14c501 100644 --- a/cadence/20240604-cadence-type-removal.md +++ b/cadence/20240604-cadence-type-removal.md @@ -21,6 +21,10 @@ the ability to remove types from their contracts as they become outdated or othe The Cadence 1.0 upgrade in particular had exacerbated this need, as the changes to access control have rendered a number of types obsolete. +However, [removing a type definition hasn't been possible/allowed](https://cadence-lang.org/docs/language/contract-updatability#invalid-changes-2) +so far, since it can create type safety issues, by being able to re-add a different type definition with the same name later. +So this motivated the necessity of having a way to tombstone a type definition. + ## User Benefit This will allow users to remove deprecated or unnecessary type definitions from their contracts. From aff797600512cf1432474659035ee5a0b5ea195f Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 30 Oct 2024 09:30:11 -0700 Subject: [PATCH 5/5] Accept FLIP --- cadence/20240604-cadence-type-removal.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadence/20240604-cadence-type-removal.md b/cadence/20240604-cadence-type-removal.md index 9a14c501..b33096b3 100644 --- a/cadence/20240604-cadence-type-removal.md +++ b/cadence/20240604-cadence-type-removal.md @@ -1,9 +1,9 @@ --- -status: proposed +status: accepted flip: 275 authors: Daniel Sainati (daniel.sainati@flowfoundation.org) sponsor: Daniel Sainati (daniel.sainati@flowfoundation.org) -updated: 2024-10-22 +updated: 2024-10-30 --- # FLIP 275: Removal of Types in Contract Updates