cadence
cadence copied to clipboard
ReadOnly(immutable) Auth Reference
Issue To Be Solved
I think it would be very nice to have ability to have readonly auth reference.
There is a very nice pattern used to query data like here: https://github.com/dapperlabs/nfl-smart-contracts/blob/6eb1b95db276e7a5efa702be601e8f541dbf2ffd/contracts/AllDay.cdc#L126
But this requires writing a lot of extra code.
Instead maybe we can have a readonly auth reference to Any. Which can return a immutable reference, which panics when someone tries to mutate.
Suggested Solution
I am thinking something like:
var t = &thing as readonly &T
This is an interesting problem. One of the reasons why we haven't introduced the notion of "constness" into Cadence is because it's inherently single-dimensional. It's constant, or it's mutable. And most people think of this as "It's safe, or it's unsafe."
In the context of a blockchain, I think that this dimension is both too restrictive, and also not fine-grained enough. I can give two examples:
- Think about a token Vault. Giving out public const access to my Vault is too restrictive, because I also want to give out access to deposits, which are mutating. But giving out read-write access to my Vault is also wrong, because I don't want to give public access to withdrawals. Const/mutable is the wrong axis for access control in this case.
- Imagine a possible future version of CryptoKitties on Flow. I might want to give you access to use my Kitty for siring, which is a mutating action. But if I do, I probably don't want you to rename my Kitty, which is a different mutating action. But I want anyone to read the name and genes and breeding count of my Kitty. So, I have three different sets of access control I want to give, const/mutable isn't fine-grained enough.
Our answer in designing Cadence was restricting access to specific interfaces. If I give &Kitty{Siring}, you can use my Kitty to get your own cat pregnant. If I give you &Kitty{KittyInfo}, you can read the genes and other metadata, but you can't do anything else with it. And, of course, I can give you &Kitty{KittyInfo, Siring} and you can do both (but not rename her!).
What's missing from this, and what I think you're picking up on, @bluesign, is that we don't have a way to let someone get a more specific, but still safe interface from a generic reference. For example, you might give me a reference to an NFT, and it'd probably be okay for me to cast it to &{KittyInfo} if the underlying NFT is actually a Kitty. Of course, Cadence can't know that &{KittyInfo} is safe, while &{Siring} is not. So it doesn't allow any down-casting without an auth reference.
One possible answer is to allow the author of a Resource class to be able to tag certain interfaces as being "safe". Again, this is somewhat implicit in other languages that allow you to cast a const reference of a base type into a const reference of a derived type. It's keeping the reference holder's access in the same "category". But, as I pointed out before, the categories for Resources are more complex than just const/mutable.
If the author of the hypothetical Kitty contract could say "KittyInfo" is safe, then you wouldn't need an auth reference, and you could get the info you need by downcasting.
Would this address the point you are getting at, or is there something else you're thinking of here?
Hey @dete, I love restrictions on composites, but it seems it is well suited to filter methods than fields. With composite fields (especially nested ones), you don't have any control over.
This is leading to habit of setting nested members ( usually every member ) private on the resource, and using getter methods which can be restricted with interfaces.
Then we have something like this: ( code duplication )
pub struct SeriesData {
pub let id: UInt64
pub let name: String
pub let active: Bool
init (id: UInt64) {
let series = &AllDay.seriesByID[id] as! &AllDay.Series
self.id = series.id
self.name = series.name
self.active = series.active
}
}
pub resource Series {
pub let id: UInt64
pub let name: String
pub var active: Bool
}
Which is super useful to convert properties to readonly structs. ( and safer )
But if we could get readonly reference to Series, It would be just:
&AllDay.seriesByID[id] as readonly &AllDay.Series
Here you can say to put:
pub let id: UInt64
pub let name: String
pub var active: Bool
in an interface, but it is fine until you have a composite field and things can get nested.
I think less code is better in general, also I believe having access to all resources ( even if I am not the owner ) in a read only way, can be useful in general.
Maybe even in the future we can even have functions to locate an NFT ( let's say by id ) wherever in the blockchain it is.
After long time, I did some thinking and came to the conclusion that single dimension is not enough @dete I agree with you on that totally.
But then I think also there is good middle ground here:
What if, when we cast to readonly, we can down-cast freely.
var kitty = borrow<&Kitty{Siring}>(from:/somewhere)
var kittyReadonly = &kitty as readonly &Kitty
and now I can access Siring with kitty, but all other properties etc with kittyReadonly.
Main problem I am lately seeing in the contracts is access(contract) (aka private) abuse. This data is on the chain, but normally you cannot access most of it.
Of course there are some obstacles like Capabilities and what will happen if they are copied and stored etc. But I think it can be somehow fixed with some creative thinking.
I believe this could now be achieved using entitlements, and two proposed FLIPs combined: https://github.com/onflow/flips/pull/89 and https://github.com/onflow/flips/pull/86
thanks @SupunS, I am closing in favor of better system.