flow
flow copied to clipboard
Extensibility flip
Adds FLIP proposing adding Extensions to Cadence. See discussion in https://forum.onflow.org/t/extensibility/622 and https://github.com/onflow/cadence/issues/357
For contributor use:
- [X] Targeted PR against
masterbranch - [X] Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
- [X] Updated relevant documentation
- [X] Re-reviewed
Files changedin the Github PR explorer - [X] Added appropriate labels
The latest updates on your projects. Learn more about Vercel for Git âď¸
| Name | Status | Preview | Updated |
|---|---|---|---|
| flow-docs | â Ready (Inspect) | Visit Preview | Aug 22, 2022 at 8:06PM (UTC) |
I am not sure of benefit vs cost here, considering what we allow is very limited.
Technically, we seem to be wrapping object via extension ( please correct me if I am wrong )
I am not sure of benefit vs cost here, considering what we allow is very limited.
Are you saying you don't think this is worth doing?
@dsainati1 I mean this opens up too many edge cases and complicated problems. Not sure if they can be covered easily.
Hence, if the original type T declared a priv field named foo, an extension E of T would not be able to declare any fields or methods named foo, even though foo is not accessible to E
for example if you make an extension, then later some time composite adds a method named âfooâ , extension is broken (even extension owner didnât change anything)
Another problems can be order of extensions, removing just one extension, requiring extension to use in another extension etc.
All those brings many edge cases, then we will hit the case of some resources donât want extensions to extend them (e.g. TopShot) which donât allow derivative works.
I love the concept of extensions, but seems like is really complicated in the current state to implement them.
for example if you make an extension, then later some time composite adds a method named âfooâ , extension is broken (even extension owner didnât change anything)
This is a fair concern, but also one that exists already in the system. If your composite conforms to an interface that you import from another contract, if the interface owner adds a method that can break your composite even though you didn't do anything. This is simply a fact of life when writing code that depends on someone else's code, I think. The right solution here would be to add a feature like contract versioning that would solve this problem generally, rather than limiting the language features we provide to users.
Another problems can be order of extensions, removing just one extension, requiring extension to use in another extension etc.
Yea, I think to make this tractable extensions will have to be order sensitive; you can only "push" an extension to the top of the "stack" or "pop" the most recently added one off. This simplifies behavior and also sets us up easily to later add the ability to have extensions require others or to allow extended types to be further extended.
we will hit the case of some resources donât want extensions to extend them (e.g. TopShot) which donât allow derivative works.
I was under the impression adding signatures to TopShot moments was one of the primary use cases for this feature. Am I wrong here? Extended versions of types would not be the same type as the original, so an extension of a TopShot moment would be no derivative of a TopShot moment than a contract that functionally duplicates the logic.
I certainly agree that this is a complex feature and will need careful consideration during design and implementation, but that isn't a reason not to do it; this would significantly improve the life of contract developers and enable an enormous number of new contracts and entire new paradigms.
Btw my initial comment was not to dismiss, I am fan of this idea from the beginning, just somehow I feel it needs to be more powerful in my opinion, if we limit the extensions capabilities too much, it will be simple wrapper.
I was under the impression adding signatures to TopShot moments was one of the primary use cases for this feature. Am I wrong here?
I mean as an example I saw that a lot, but they are too protective on the IP, so I am not so sure.
I certainly agree that this is a complex feature and will need careful consideration during design and implementation, but that isn't a reason not to do it; this would significantly improve the life of contract developers and enable an enormous number of new contracts and entire new paradigms.
I totally agree.
This is a fair concern, but also one that exists already in the system. If your composite conforms to an interface that you import from another contract
I think here is situation a bit reversed, in my imagination, here we have NFT has some extensions and NFT is hosting them somehow. So maybe isolating them from each other and NFT can be nice.
I mean something like nested resources:
pub struct S {}
pub extension E for S {
pub let x: String
init(_ x: String) {
self.x = x
}
}
x can be accessed like S.E.x and somehow E can access S fields with something like S.some_field
I think most powerful usage of extensions will be to extend the native functionality ( by hooking some functions ) So somehow extension should be able to override the existing methods.
I think most powerful usage of extensions will be to extend the native functionality ( by hooking some functions ) So somehow extension should be able to override the existing methods.
This is an interesting idea, which would completely change the fundamental behavior of extensions as outlined in this particular proposal. At the moment the extension are designed such that an extended version of a type is still a valid instance of the original type; if you add a hat to a CryptoKitty, your Kitty still functions as a regular Kitty for any and all applications that use Kitties that don't know about hats. Similarly, if you attach a signature to your TopShot moments, your moments still function as moments for all contracts that interact with regular moments, but also can be used in places where a signature is expected as well.
Were we to allow extensions to override existing methods, this property would no longer hold; an extended Kitty could no longer be trusted to function anything like a Kitty at all. I'm interested in the idea, but I'm curious what powerful usage this would enable that is worth sacrificing this property.
Yeah this works with isolation of the extensions mostly, standalone it is problematic.
Let's say you have Kitty. And someone developed some KittyHat Extension.
Technically you will have something like this :
var kitty <- Kitties.GiveMeKitty()
var hat <- KittyHats.GiveMeHat()
kitty.extend(<-hat)
// kitty will be type of CryptoKitty
// kitty.kittyHat will be type of CryptoKitty+KittyHats
Here kitty.kittyHat for example can have different MetadataView. ( Putting a hat on kitty item possibly :) )
Or if we have meow() method on CryptoKitty, CryptoKitty+PirateTalk can have different meow()
Main issue is we relied on functions too much lately ( with metadata, new NFT standard etc ) extending without extending the functions will be little problematic.
I am more thinking like what would be cool to extend TopShot, FlowToken Vault etc ? then trying to get back to technical realm. For Vault, I would like to apply an extension to something on deposit for example ( send to other people, or convert to USDC etc )
Method overloading/overriding, and dynamic dispatch in general, is something Cadence explicitly did not include in the language for security reasons (see https://developers.flow.com/cadence#security); it is important that when a developer calls a method that there is no ambiguity about which method is being called, and what it is going to do. I don't think we would want to sacrifice this property for any reason that I can think of.
@bluesign
I am not sure of benefit vs cost here, considering what we allow is very limited. [...] I mean this opens up too many edge cases and complicated problems. Not sure if they can be covered easily.
There are still some open questions, but I feel confident that we are going to come up with a proposal that answers them, as well as a proposal that is simple and effective.
Thank you for your feedback.
@dsainati1 Thank you very much for working your way through the community's earlier discussion about such a language feature, resolving some of the open questions, and writing up a concrete proposal for the requested functionality (adding functionality and data to an existing type without modifying its source). Great work!
I read through the proposal a couple of times. The proposal makes sense to me. As @bluesign pointed out, it still has some open questions which I want to quickly summarize (please correct me if I got these wrong):
- Clash when extended type is updated in a way that overlaps with extension
- Example:
- Extension E, which has field f, is added to extended type T
- T is updated, field f is added
- This assumes that it is possible to add fields to existing types in contract updates. This feature is being proposed in FLIP #1097. We should have provisions for the case it gets accepted (very likely)
- There are a few things we could do here:
- Forbid T to be updated. Not tractable, as to determine if a type can be updated, all instances need to be checked for conflicting extension
- Accessing f on T changes semantics to now refer to T.f instead of E.f, meaning code might break as a result of an update. This will likely be surprising to developers and thus not a good option either
- Example:
- Dependencies between extensions and removal of a dependency
- Example:
- Type T is extended with E1, resulting in T with E1
- Type T with E1 is extended with E2. E2 depends on T and E1
- Extension E1 is removed from Type T with E1, E2
- Options:
- Treat the concrete list of extensions as a stack
- We could simply disallow E1 from being removed. This can be analyzed statically. However, it might be surprising to users or even developers, that adding one extension will block the removal of another.
- Example:
- Some resources don't want to be extended
- The proposal should probably explicitly state that there is no mechanism for types to prevent extensions. The user owning the data should be in control and should be able to freely extend (as long as it is a compatible extension).
As far as I understand, @bluesign's concern is that a proposal which attempts to follow the "combined" approach will likely have (too) many restrictions, reducing the usefulness to just a small set of use-cases. That's a fair concern.
While re-reading the discussion in the forum, this proposal and the feedback for it, as well as considering the recent addition of the interface default functions, it occurred to me that we are probably unnecessarily trying to come up with a solution that looks similar to the related work, i.e. "similar" features in other languages.
It seems like much of the complexity of the current proposal and the outstanding problems stems from the approach of attempting to "combine" the extended type with its extensions.
Maybe it is worth to consider a "composition" approach, where types are not merged, but the extended type stays as-is, and extensions are independent types, yet they still get access to the extended type.
@bluesign hinted at this above ("x can be accessed like S.E.x and somehow E can access S' fields with something like S.some_field").
Today, if a contract author wants to explicitly provide support for extensions, they can and would probably implement this by leveraging composition:
resource Kitty {
let extensions: @{Type: AnyResource}
}
The proposal could maybe be as simple as providing built-in support for this notion, for any type.
resource/struct ... {
// internal field managed/stored by language, not accessible by programs
let extensions: {Type: Any}
// add/get/remove operations are defined for all types by the language, in the language.
// They could be accessible by programs or only accessible through dedicated syntax
fun extend<T>(_ extension: T): T? {
let existing <- self.extensions[extension.getType()] <- extension
return existing
}
fun getExtension<T>(): auth &T { ... }
fun removeExtension<T>(): T? { ... }
}
// Example usage if functions are exposed
// (not backwards compatible, function might already exist in existing types)
let r: @R <- create R()
let e: @E <- create E() // initializer and functions of `e` which access `parent` get `nil`
let oldE <- r.extend(<-e)
destroy oldE // no previous extension with that type
let eInR: auth &R? = r.getExtension<@E>() // non-nil, extension exists
// Example usage with special syntax (strawman syntax; benefit: no clashes with existing functions
let r: @R <- create R()
let e: @E <- create E()
let oldE <- extend r with <-e
let eInR: auth &R? = E of r
This solves the conflicting member problem, as types stay independent, between the extended type and the extensions, as well as between extensions.
To allow an extension (basically a child of the extended type) to access the extended type (its parent),
the extension declaration could implicitly declare a field let parent: auth &T? which is non-nil while the extension is attached.
Such an approach will likely require less additional syntax and semantics and potentially allow the realization of extension use-cases in the same way.
@turbolent in the "composition" approach, is it possible to: fallback to parent for fields and methods when they are not implemented.
this way we can have very powerful extensions. I tried to give some examples above like CryptoKitty+PirateTalk.
For example if kitty has meow() method, if extension doesn't implement the meow() we call Kitty meow(), but if extension implements a new meow() we call that instead.
So instead of extensions being window( e.g. views) to the resource, they can be real extensions to the resource.
This use case can be better example:
FlowToken.Vault + LimitedWithdraw extension, can allow limited amount of tokens to be able to be withdrawn from the Vault.
FlowToken.Vault.LimitedWithdraw will also implement all interfaces FlowToken.Vault implementing automatically. ( As functions and fields are backed by parent ) Also can hook into withdrawal and keep a track.
It till not be FlowToken.Vault , but it will be FlowToken.Vault.LimitedWithdraw type. But still it will support the interfaces. If we can possibly find a way to give a capability to extension, it can be really powerful.
Thanks for the comments on the FLIP so far; I have updated the FLIP in response to some of the open questions. In particular:
Dependencies between extensions and removal of a dependency
The original plan was indeed for dependencies to form a stack, and that an extension with type R with E1, E2 would need to have E2 removed first, and E1 to be removed after. However, @turbolent pointed out to me in a meeting that if we can upcast extended types (e.g. R with E1, E2 to R with E1), then there is no way to enforce this statically, since we can't know from a type what the top extension on the "stack" actually is.
I have updated the FLIP to have extensions be order-insensitive, and for R with E1, E2 to be interchangeable with R with E2, E1. This also means that extensions cannot depend on each other at all. This may be something we want to add in the future; but this would be coupled with the ability to extend an already extended type. How exactly this would work though is out of scope for this proposal specifically.
Clash when extended type is updated in a way that overlaps with extension
I also think this is out of scope for the FLIP, since as I pointed out in an earlier comment this is not a problem unique to this FLIP; contract updates can already invalidate existing code and force developers to handle them. As is, the extension would become semantically invalid, and would be rejected by the type checker.
IMO this is a topic that deserves its own attention and a dedicated solution to help developers migrate their code in response to updates that break their contracts.
Do we want to add the ability to dynamically get a list of all the current extensions on a type?
Discussions with @turbolent also indicated that this is a functionality we will probably want, but also that this would be out of scope for this FLIP, for the sake of simplicity. A future FLIP can propose such a feature and describe how it would work after this FLIP adds the base functionality.
I also wanted to briefly respond to @turbolent's alternative proposal; I have added it to the alternatives section of the FLIP. I like that it resolves the name conflict problem, but I think it has a critical drawback, which is that it does not provide any static typing guarantees. The proposal in the FLIP has the nice property that the extensions present on a value are expressed in the type of that value; this makes it possible for a contract developer to write an application that is designed to work with, say, CryptoKitties with Hats, and statically require its inputs to be of that extended type. If extensions are stored on the base type like in the alternative though, expressing the type of a CryptoKitty with a Hat statically is no longer possible, and the author of such an application would only be able to statically require its inputs be CryptoKitties, and then would need to dynamically validate that the Hat extension is present on each of them.
I also like that the existing proposal preserves the property that an extended type is functionally equivalent to a combination of its base and extension types. I.e., if I extend a CryptoKitty with a Hat, I can use that exactly the same way that I would use a version of the CryptoKitty type that had included all the Hat functionality on it originally.
Another question that has come up: at the moment this FLIP requires extensions to have the same kind as the type they extend; i.e. all extensions of a resource are resources, and all extensions of structs are structs. Is there a use case for allowing extending a resource with a struct?
this makes it possible for a contract developer to write an application that is designed to work with, say, CryptoKitties with Hats
This shouldn't be possible, "CryptoKitties with Hats" shouldn't be a Type.
I think problem here is you are looking more from a CS perspective ( classes, types etc ) than UX side of things. We are not designing general purpose language Cadence in my opinion.
I prefer any function taking CryptoKitty then checking dynamically if it has hat. And this is the way to do things in Cadence. So you can later maybe accept CryptoKitty with Scarf too.
I mean CryptoKitty is being NonfungibleToken, and some contract accepting NonfungibleToken and dynamically checking if it is CryptoKitty is our current suggested design after all.
Using instead of isInstance, hasExtension is not too hard.
That's why I suggested first making a list of valid use cases, then evaluate solutions, if they cover the use cases.
Another question that has come up: at the moment this FLIP requires extensions to have the same kind as the type they extend; i.e. all extensions of a resource are resources, and all extensions of structs are structs. Is there a use case for allowing extending a resource with a struct?
if it was up to me, I would limit extension as "resource extensions"
I had another suggestion inspired by how Scala does their extension system - the biggest addition being that extensions can directly reference the resource they are extending (see extension in 0x02)
// 0x01
pub contract Base {
pub resource interface Public {
pub let a: Int
pub fun f()
}
pub resource Thing: Public {
pub let a: Int
pub let b: Int
pub fun f(): {}
pub fun g(): {}
init() {
self.a = 10
self.b = 20
}
}
}
// 0x02
pub contract Extension {
pub resource interface Public {
pub let c: Int // Edited
pub fun h() // Edited
}
pub extension Extras(m: @Base.Thing{Base.Public}): Public {
pub let c: Int
pub let h(): Int {
m.f()
return m.a // from Base.Public
}
pub let i(): Int {
m.g() // error
return m.b // error
}
init() {
self.c = 30
}
}
}
// TX
import Base from 0x01
import Extension from 0x02
transaction() {
prepare(acct: AuthAccount) {
let base <- Base.createThing()
let extended <- Extension.createExtras(<-base)
let extended2 <- Extension2...(<-extended)
acct.save...(<-extended2) //
// Retrieve from account
let base = acct.get...<@{Base.Public, Extension.Extras}>
base.f() // from Base.Public
base.g() // error
base.h() // from Extras
}
}
the biggest addition being that extensions can directly reference the resource they are extending
Perhaps I am misunderstanding, but I believe the proposal already allows for this: an extension declared pub extension E for B would have the methods and fields of B available on its self value (outside of initializers and destructors). Or is the difference that in your example the reference to m is separate from the self reference of the extension?
Otherwise, I believe the proposal is quite similar to the behavior you have in your example above, with slightly different syntax, with the exception that Extras would not be allowed to redeclare f, as Cadence does not have any plans to support function overriding (https://developers.flow.com/cadence#security).
@siddthesquid yeah this is the wrapper approach actually, every extension is wrapping the resource they are extending.
Problem here was type system, ex: extended2 will be subtype of base or not ?
Cadence does not have any plans to support function overriding
Cadence is acting little bit unreliable here in my opinion @dsainati1 ( not telling this to insult anyone, as English is not my native language can be unintended meaning ). We just merged interface default conditions.
Like 1-1.5 year ago I actually I suggested to write down this principles in a way of a constitution, but it didn't gain any positive traction. Always something there in the docs from 2-3 years ago, but ship has already sailed. "
Let's have some principles and stand behind them no matter what" I can stand behind, but this is little ridiculous.
I mean we had the same with msg.sender, msg.sender considered harmful but someone though it is smart to put owner on resources. ( no offence again )
Problem here was type system, ex: extended2 will be subtype of base or not ?
In the FLIP as it is currently, extended2 would be type Base with Extras, Extras2, which would be a subtype of Base; i.e. you can use it anywhere a Base is expected, as all the methods of Base would still work as normal and there is no risk of overriding.
If I am understanding your version correctly though @bluesign, here extended2 would still have type Base and also a list of extensions on it that would have type Base+Extras and Base+Extras2?
Problem here was type system, ex: extended2 will be subtype of base or not ?
In the FLIP as it is currently,
extended2would be typeBase with Extras, Extras2, which would be a subtype ofBase; i.e. you can use it anywhere aBaseis expected, as all the methods ofBasewould still work as normal and there is no risk of overriding.If I am understanding your version correctly though @bluesign, here
extended2would still have typeBaseand also a list of extensions on it that would have typeBase+ExtrasandBase+Extras2?
ah I was answering to @siddthesquid's example. ( wrapper kind of )
In my case, I don't think extension should be a specific type, it should be managed with hasExtension instead of isInstance
Or is the difference that in your example the reference to m is separate from the self reference of the extension?
@dsainati1 I completely overlooked that the extending resources have self. to access their parent members, in which case your model is much cleaner. Also, I didn't mean to override in Extensions.Public - those were meant to be renamed to the members defined in that resource (c and h), my bad!
Problem here was type system, ex: extended2 will be subtype of base or not ?
@bluesign It would have been a subtype of whatever it was wrapping. So I was thinking it could also just be Base.Public (and not even Base.Thing). Honestly, a lot of my logic goes out the window when trying to think about how updating contracts affects all this as well, so I haven't thought about those concerns at all
One thing I'm curious of is that if multiple "Extension developers" make an NFT extension with overlapping functions, would a resource be allowed to extend both of those?
Also, if a transaction wants to use the extended functions of an extended resource, does the "Extension" contract have be imported explicitly?
Btw I like this idea - I'm currently trying to do a makeshift version of the dictionary approach for my current contract (i haven't finished so don't know if it'll work, but this would have been easier with extensions)
Cadence is acting little bit unreliable here in my opinion, .... We just merged interface default conditions.
Default interface conditions are not quite the same thing as full method overriding. Interfaces are "abstract" in the sense that you cannot instantiate them; if you have some interface A you cannot have a value that is just an A. For this reason developers know when they create an interface that the methods in it will need implementation. Similarly, if I write some function with an A parameter, as a developer I know that I cannot predict with any certainty what any of the methods on A will do, because I know that A is an interface. In effect, because we only allow "overriding" like this on abstract types, developers are not mislead about the behavior of code.
However, if you have some composite B right now, we know with absolute certainty that if you call B.foo it will always execute the same code. To borrow a term from Java, all structs and resources in Cadence are final. This is a nice property to have because it makes code predictable. To allow extensions to override methods in concrete composites would break this property.
@dsainati1 I am considering you reference:
Security is a consideration when interacting with other smart contracts. Any external call potentially allows malicious code to be executed. For example, in Solidity, when the called function signature does not match any of the available ones, it triggers Solidityâs fallback functions. These functions can be used in malicious ways. Language features such as multiple inheritances and overloading or dispatch can also make it difficult to determine which code is invoked.
Similarly, if I write some function with an A parameter, as a developer I know that I cannot predict with any certainty what any of the methods on A will do, because I know that A is an interface
But this is nothing bad, even we encourage this usage no ? We want things to be composable. But this "makes it difficult to determine which code is invoked." ( which is the reason of we don't allow "overloading or dispatch".
Btw I don't defend extensions to override methods, I think extensions should be separate type, where methods fallback on to parent.
If I have kittyItem, kittyItem+hat (extension) will not be subtype of kittyItem.
but if you take extension reference: extension = kitty.getExtension(hat), it will behave like kittyItem.
One thing I'm curious of is that if multiple "Extension developers" make an NFT extension with overlapping functions, would a resource be allowed to extend both of those?
Yes; if two different developers wanted to make a Hat extension for CryptoKitties, you could have a kitty that has both hats on it at the same time. You would just not be able to reference both extensions at the same time if they declare incompatible/overlapping fields or functions.
Also, if a transaction wants to use the extended functions of an extended resource, does the "Extension" contract have be imported explicitly?
Yes, in order for these functions to be available the type of the extension would need to be known, meaning the Extension contract would need to be imported, I believe.
But this is nothing bad, even we encourage this usage no ? We want things to be composable. But this "makes it difficult to determine which code is invoked." ( which is the reason of we don't allow "overloading or dispatch".
The difference is whether the behavior is expected or surprising. Interfaces being implemented is expected by users, while composites being overridden is not expected, so if it were possible it would be surprising to users and hence potentially dangerous. That said it seems like we are in agreement that extensions would not be able to implicitly override base type methods; it seems like you are suggesting that you would need to explicitly call the method on the extension itself in order to get the fallback behavior, which seems fine.
âimplemented is expected by usersâ is tricky concept though. If we say âuserâ is who is reading the code, in practice there is no difference.
I dont think people capable of reading Cadence is big enough to have a concern of this.
To summarize the discussion here for those following along, currently the largest point of debate is over whether or not extending a resource should change its type. The two models we have are:
-
Static model: Extending a value of type
Twith an extension of typeEproduces a value with typeT+E(orT with E, or whatever other syntax you prefer).T+Eis a subtype ofT, and can be used in places where onlyTis expected, although of course only methods and fields onTwould be accessible in that context. To connect this back to a concrete example, a extending aCryptoKittywith aHatwould produce a new resource of typeCryptoKitty+Hatthat represents the kitty extended with the hat functionality.CryptoKitty+Hatresources would be usable in by contracts that expect aCryptoKittyonly, and contracts or applications that wish to work with onlyCryptoKittys withHats would also be able to statically restrict their inputs to resources of this type. This is the behavior that the FLIP currently proposes. -
Dynamic model: All resources would store the extensions attached to them internally, and extending a value with a resource would not change its type. Given some resource of type
T, there would be no static information present about what extensions are present on the value; instead the extensions on a value would be dynamically inspectable with functions likegetExtensions,hasExtensionor similar. In this model, aCryptoKittywith aHatwould still be a value of typeCryptoKitty, butCryptoKitty.extensionsor some similar field would contain aHatvalue. Users wishing to work with aHat-extended kitty would write a function accepting values of typeCryptoKitty, and then dynamically check the extensions of their input to find the appropriate extension. This is the behavior @bluesign has been describing (although please correct me if I have missed something in the summary here).
Very nice summary @dsainati1, I have few minor things to add for comparison.
Static model
In this model: We cannot extend (modify) methods, but we can add new ones. Actually this is the initial point where my concern was started.
- For example:
CryptoKitty+Hatresource cannot implement newMetadataViewsor cannot modify existing ones. ( So allCryptoKitty+Hatwill look likeCryptoKitty)
There is a very big chance of conflicting extensions, in my opinion if 2 extensions implement an interface the base type doesn't implement, they would not be compatible.
pub struct interface I {
pub fun foo(): Int
pub fun bar(): String
}
resource R {
fun foo() {}
}
extension E1 for R: I {
fun bar() {}
}
extension E2 for R: I {
fun bar() {}
}
Also there is a breaking situation when R decides to implement I in the future. I think we need to clear up how resources will behave in that situation. Preventing to apply extension is ok but what will happen to resources already out there with extension applied?
if I has priv member, and R already implements I, if E1 implements I, cannot access priv member right?
Btw I love the static type guarantees and X with Y approach, but I just think it is too much limiting to do anything creative.
Also some positive feedback:
R with E being R can be really useful and can be used a bit like below.
pub resource Collection{
}
access(contract) extension CollectionNFTPublic for Collection: ExampleNFTCollectionPublic, NonFungibleToken.CollectionPublic{
}
access(contract) extension CollectionNFTProvider for Collection: NonFungibleToken.Provider{
}
access(contract) extension CollectionNFTReceiver for Collection: NonFungibleToken.Receiver{
}
access(contract) extension CollectionMetadata for Collection: MetadataViews.ResolverCollection{
}
This gives ability to NFT creator to upgrade NFT or CollectionMetadata with micro-transactions. Upgrade your NFT to support VR for X FLOW, or remove royalties for X FLOW etc.
Also there is a breaking situation when R decides to implement I in the future. I think we need to clear up how resources will behave in that situation. Preventing to apply extension is ok but what will happen to resources already out there with extension applied?
Updating a resource with a new method is fine, as code is not saved; existing extended resources will just become invalid statically, and extending R with E in also fail the type checker, but their data will be fine because functions are not stored as data anyways. Additionally, if/when https://github.com/onflow/flow/pull/1097 allows adding new fields to resources in contract updates, we should still be safe if we namespace fields internally. The end of the Extended Types subsection of the FLIP says:
At runtime, fields and methods are namespaced, so if `E1` and `E2` both declare a field named `foo`, so while a type `S with E1 and E2` cannot be created, `E1` and `E2` can still both be attached to `S` at the same time at runtime
Basically what this means is that internally the foo field on the two different extensions is stored separately, and won't ever have conflict with any number of foo fields that may be added to the extension. This should in theory also hold true if foo is added to the base resource. The restriction is that multiple extensions that declare the same field/method cannot be used at the same time, i.e. you can't ever have a static type where both E1 and E2 are part of the type, even if the underlying runtime type does have both extensions present. Adding foo to the base resource would statically invalidate any existing extensions that add a foo field, but there would be no need for any kind of data migration. What we would likely need is a way to remove extensions from resources that are themselves already invalid in order to make them usable again.