Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FLIP for entitlements and safe reference downcasting #54

Merged
merged 34 commits into from
May 23, 2023

Conversation

dsainati1
Copy link
Contributor

@dsainati1 dsainati1 commented Dec 16, 2022

This adds a FLIP proposing a Cadence change adding a new entitlement construct, a new access control modifier using entitlements, and changing the behavior of references to permit safe downcasting.

cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved

The second, more complex part of this proposal, is a change to the behavior of references to allow the `auth` modifier not to
refer to the entire referenced value, but to the specific set of interfaces to which the referenced value has `auth` permissions.
To express this, the `auth` keyword can now be used with similar syntax to that of restricted types: `auth{T1, T2, ...}`, where the `T`s
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will the old syntax (i.e: without curly braces) continue to be supported, say for restricted types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally the old syntax would become deprecated eventually, as currently the only reasonable way to interpret it is as a reference that is auth over the entirety of the referenced type, which in the new model would be a very dangerous behavior to have as a default. What would be the use case for keeping the old auth syntax that does not require a restriction set?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sweet, then we can reuse the restricted types syntax when we add algebraic effects 🥲

Then, someone with a `&{Balance}` reference would be able to cast this to a `&{Receiver}` and call `deposit` on it.
They would also be able to cast this to a `&Vault` refrence, but because this reference is non-`auth` for `Provider`,
they would be unable to call the `withdraw` function. However, if a user possessed a `auth{Provider} &{Balance}` reference,
they would be able to cast this to `auth{Provider} &{Provider}` and call `withdraw`.
Copy link
Member

@SupunS SupunS Jan 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auth{T} &{U} syntax is a bit confusing to me.

My first concern is, in a concrete type (struct/resource) scenario, the reference-taking syntax is let ref = &r as auth &T, but for a restricted type, the user has to specify the type twice let ref = &r as auth{T} &{T}. Maybe make the T in auth{T} optional if both the auth-type and the restricted type are the same? Anyway they can't downcast it further I guess.

The second concern is it is easy to confuse the type of the resulting reference. For eg: auth{Provider} &{Balance} made me wonder whether I can (or why I can't) call withdraw on the resulting reference.
I don't really have any suggestions for an alternative syntax, but if we can find one/improve this to iron out any potential confusion, that would be great.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree the syntax is a little bit confusing; at first glance it seems like you have to specify the type twice (once in the auth{T} and once in the &{T}, but they are really doing quite different things. I think it is going to be quite rare for users to want to declare a reference value this way, however. In fact, with these changes there is little reason to ever actually create a reference value with anything other than its underlying concrete type: let ref = &r as auth{Provider} &Vault makes a lot more sense than let ref = &r as auth{Provider} &{Provider, Receiver} given that reference types can now be downcast. The "security" aspect of restricted types is effectively being moved to the auth part of the type, while the actual reference type itself is now only being used for interface conformance checking.

I suspect that if we implemented this change, we would only really see restricted types being used as supertypes; i.e. if I want to write a function that operates over all Providers I would write it as fun(_ p: {Provider}).

Copy link
Member

@turbolent turbolent Jan 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially had feedback similar to Supun's, but ended up not posting it, so it's great it came up again:

Restrictions (Us in T{Us}) are not really restrictions anymore, as downcasting is now allowed.

We still might want to express something like T{Us} (where Us is a set of interfaces) for the case where T is AnyStruct/AnyResource. Maybe we should just make the syntax {Us} and call it an "interface set"?

The continued existence of restricted types (T{Us}) maybe cause confusion for developers, so maybe we should just get rid of them along the way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I really like this idea; the interface set type makes a lot of sense and is more intuitive than restricted types anyways.

Copy link
Member

@SupunS SupunS Jan 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With restricted types gone, {Us} sounds more like an 'intersection type' 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, {Us} has been always pretty much like TypeScript's intersection types. Swift has protocol composition, a similar feature (even uses the same U1 & U2 syntax as TS), though there such a type is not first class (e.g a variable cannot have such a type)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for changing restricted types to intersection types. Allowing interfaces in type position (even if they need to be qualified somehow) would obviate the need for a separate Interface proxy type, making Cadence issue #2233 trivial to implement

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe replacing restricted types with standalone intersection types should be its own FLIP?

Copy link
Member

@turbolent turbolent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great proposal! 👏

I read through everything except for the attachments discussion point.

In general this a great proposal, with great user benefits: it improves usability, i.e. working with references; and at the same time also increases safety / reduces accidents / the potential of foot-guns. It would be great to get the proposed changes in 👍

Maybe: In the alternatives considered, describe the earlier idea to allow implementors of concrete types to specify auth for interfaces in the conformance list and compare it to the current proposal:

  • The earlier idea had one "decision point"
    • Interface authors did not have to, but also could not specify which functions are auth or not
    • Implementors of concrete types could specify auth for interfaces in the conformance list
    • Issuer of references did not have to, but also could not specify which interfaces are auth or not
  • The current proposal has a mix of "decision points":
    • Interface authors must determine which functions should be authorized
    • Implementors of concrete types do not have to, but also cannot make any decisions (probably OK)
    • Issuers of references can decide auth restrictions

The biggest issue for the proposal is not the proposed changes themselves, but its rollout, i.e. the compatibility problems: interface authors need to change access modifier from pub to auth, e.g. for FungibleToken.Provider. I do not think this is a blocker, but will be a challenge.

The second biggest issue is the effort the implementation effort: The existing reference and restriction rules and their implementation are very complex and need to be removed, the new rules are complex too. As this changes the core primitives of access control, the new implementation must be absolutely secure.

Thank you for adding the discussion points at the end. I quite like the idea mentioned at the end, to issue auth/non-auth references based on path domain.

cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved
cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved
cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved
cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved
cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved

Currently in Cadence, when users are in possession of a reference value, unless that reference is declared
with an `auth` type, users are unable to downcast that reference to a more specific type. This restriction
exists for security reasons; it should not be possible for someone with a `&{Balance}` reference to downcast that
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe make a general point, and then mention (and link to) the Fungible Token standard contract as a concrete example, before referring to specific types defined in that contract – readers may not (immediately) know what those types (Balance, Vault) refer to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems likely to me that anybody reading or contributing to a language FLIP for Cadence or Flow is aware of the basic Balance, Vault, Provider and Receiver types.

to a `&Vault`. Cadence's current access control model relies on exposing limited interfaces to grant others capabilities
to resources they own.

However, this can often result in restrictions that are unnecessary and onerous to users. In the above example, a user
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Fungible Token contract is a great running example 👍

Maybe mention that:

  • Balance provides access to the read-only field balance
  • Receiver provides access to a mutating function deposit, but that mutation is "safe" in the sense that a call by any party is OK
  • Deposit provides access to a mutating function withdraw, but that mutation is "unsafe", it should not be possible for any party to call the function, only certain authorized parties

The Balance and Receiver types are already mentioned, but maybe help the reader and explain why their use is sade, so they do not have to look up the details in the code, and also mention the Deposit type explicitly and why access should be restricted.

cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved
cadence/2022-12-14-auth-remodel.md Show resolved Hide resolved
cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved
@dsainati1
Copy link
Contributor Author

The biggest issue for the proposal is not the proposed changes themselves, but its rollout, i.e. the compatibility problems: interface authors need to change access modifier from pub to auth, e.g. for FungibleToken.Provider. I do not think this is a blocker, but will be a challenge.

Yes, I think this is going to be by far the hardest part of the change; I believe we should evaluate the proposal on its own merits, but I also think that "this is a good idea but it's not feasible to roll out" is also a reasonable stance to take here.

@dsainati1 dsainati1 marked this pull request as ready for review January 4, 2023 21:26
Co-authored-by: Naomi Liu <[email protected]>
Co-authored-by: Bastian Müller <[email protected]>
@bluesign
Copy link
Collaborator

bluesign commented Jan 7, 2023

This is a great proposal but how to migrate storage ? Stored capabilities with restricted references for example.

@dsainati1
Copy link
Contributor Author

This is a great proposal but how to migrate storage ? Stored capabilities with restricted references for example.

This is a great question and probably one of the more challenging parts of this problem. One potential option would be to transform any existing references in storage into the new format based on their borrow type. I.e., any old reference with type &T would be transformed into a auth{T} &T. The idea here being that in the old model of Cadence, when you gave someone a reference with a borrow type T, you were allowing them access to all methods and fields on that type. Hence, any existing references created in that old model should be made auth for their entire borrow type to keep the same level of permissions without adding any more.

@dsainati1
Copy link
Contributor Author

dsainati1 commented Jan 9, 2023

Feedback from @sisyphusSmiling on the new attachments preview release has identified an attachments use case (in addition to the iteration case we already know about) that would benefit a lot from this new feature. In the demo he is working on, he has an attachment for INFTs that manages some data in a struct, and has two functions getRecord and resetRecord to view and to modify the record struct, respectively.

In his use case, he'd like to be able to call getRecord from any reference to the attachment, regardless of whether it was accessed from a reference to the base resource or directly from the base resource. However, he wants to restrict access to resetRecord to only the owner of the base resource. Currently this is difficult (or impossible) to do in Cadence, since the way attachments currently work preserves no information about how the attachment was accessed inside of their member functions. With the changes proposed in this FLIP however, resetRecord could be declared with an auth access modifier:

pub attachment R for NonFungibleToken.INFT {
    pub fun getRecord(): AnyStruct { ... }
    access(auth) fun resetRecord() { ... }
}

Then, someone with just a reference to the base b would be unable to call resetRecord, because b[R] would not result in an auth reference to R. However, if they owned b (or if b was declared to be auth for INFT), then b[R] would result in an auth reference to R and thus resetRecord would be accessible.

@dsainati1
Copy link
Contributor Author

However, one suggestion we have received is to require that the mappings be used only in the definitions that declare them

Regarding this proposed example above, after considering this and starting work on a prototype for it, I realized that allowing entitlement mappings to be declared inside structs and resources necessitates the question of how these values are inherited from interfaces and how conformance checks work for them. This introduces unnecessary complexity with unclear benefit, so unless there is a compelling reason to do otherwise I am going to opt against this proposed change.

dete
dete previously requested changes May 2, 2023
A single member definition can include multiple entitlements, using either a `|` or a `,` separator when defining the list.

An entitlement list defined using a `|` functions like a disjunction (or an "or"); it is accessible to any `auth` reference with any of those entitlements.
An entitlement list defined using a `,` functions like a conjunction set (or an "and"); it is accessible only to an `auth` reference with all of those entitlements.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this section should have some note on precedence (eg. How do we interpret auth(A, B | C).) FWIW- My suggestion is that any mix of AND and OR operators must include parens for precedence (making my example a syntax error that would require the author to explicitly write auth((A, B) | C) or auth(A, (B | C)).

I also think that + would be better than , for this: eg. auth(A+B) instead of auth(A,B).

Copy link
Contributor Author

@dsainati1 dsainati1 May 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will specify this in the FLIP directly, but the reason there's no note on precedence for these operators here is because you cannot mix them: for simplicity's sake an entitlements set can either be a disjunction or a conjunction, but not both. This is both a simplification for the users (especially because the actual use case for a mixed set would be very rare) and also on the implementation side (since we won't need to implement a full-on boolean algebra solver to decide entitlement relations).

As for +, I think this would be confusing, since + often means "or" in other languages, especially those with algebraic types, where the "sum" type represents the union/disjunction between two types and the "product" type(which is often denoted with a , like in a tuple type) represents the intersection/conjunction.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally i feel that || and && are more natural operators to use.

cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved
cadence/2022-12-14-auth-remodel.md Show resolved Hide resolved

let entitledRef = &r as auth(OuterEntitlement) &OuterResource
let entiteldSubRef = r.ref // `OuterEntitlement` is defined to map to `SubEntitlement`, so this access yields a value of type `auth(SubEntitlement) &SubResource`
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm. This might point to a bigger problem. Using references as data members of Resource objects isn't really great for this example, either.

But! It raises a bigger question. If the standard way of storing a Resource inside a Resource is not via a reference, what happens here:

resource R {
    access(all) let inner: auth(X) @SomeType

   access(all) fun typeFun() {
      self.inner   // what is the type of this statement?
   }
}

fun test(r: &R) {
   r.inner  // What is the type of this statement?
}

You'd think that self.inner would be type @SomeType. And, intuitively, I would say that r.inner would be of type auth(X) &SomeType. But why is one a reference and one direct access? Could we say that self.inner is actually auth(owner) &SomeType instead(at least, when used as an r-value)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I understand the question here; the auth(X) @SomeType can't actually exist in the language (unless you are proposing here that we add it), as only references can posses an auth modifier. I've rewritten this example to use accessor functions, so hopefully this will clarify the point more?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we talk live about this? One of the big motivations for the mapping feature was to make using the composition pattern much smoother in Cadence. If a Resource that contains a Resource can only grant access to the inner Resource via a method call, I feel like we're falling short. Maybe it's a future enhancement tho?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was discussed as part of the "external mutability" discussions we have recently. There, one idea was that field access on owned fields keeps resulting in the owned type (in typeFun, self.inner has type @SomeType), as it does currently, but external field access on references results in a reference (in test, r.inner has type &SomeType).

Agreed that it might be better to discuss this topic separately from this FLIP.

If this were to succeed, then a user could create an `OuterResource` with only a `B`-entitled reference to `SubResource`, and use the mapping on it to get a `D`-entitled
reference to the `SubResource`, since the owner of the `OuterResource` can get any entitled reference to it. In order to be a valid initial value for the `self.ref` field here,
the `ref` argument to the constructor would need to have type `auth(B, D, E) &SubResource`. This is most easily achievable if the creator of the `OuterResource` is also
the owner of the inner resource.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this Flip should include a minimal set of entitlements for the built-in containers, and this would be a great time to include a concrete example of how they could be used, which I think would motivate the reader to better understand the power of mappings.

So, assume that we have Insert, Replace, and Remote entitlements for the built-in dictionary types. A neat example of using mappings would be something like this:

entitlement mapping UserListUpdates {
  AddUser -> Insert
  RemoveUser -> Remove
  UpdateUser -> Replace
  all -> all
}

access (UserListUpdates) let userList: auth(UserListUpdates) userList{String: UserData}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe @SupunS was looking into potentially adding entitlement support for using entitlements to handle mutability on built in container types. I am in favor of adding these, but IMO we shouldn't expand the scope of this particular FLIP any further than it's already grown. These can be proposed in a follow-up FLIP that expands upon the existing entitlements to add them to containers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: using entitlements to address the "external mutability" problem is a great idea, and so it proposing entitlements for built-in types.

However, such proposals should probably be done in separate FLIP(s), as this is one is already very large.

Let's continue discussing these ideas as part of the "external mutability" problem discussions.

r.foo()
let ref: auth(E) &R = &r
ref.foo()
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll be honest. I would love to remove or at least deprecate pub and priv. If this code examples (for example) used access(self) and access(all) it'd be easier to parse (visually, as a human) and more consistent.

pub resource R {
    access(E) fun foo() { ... }
    access(all) fun bar() {
        self.foo()
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree 100%. I'll update the examples in the FLIP, but IMO a proposal to remove and replace pub and priv deserves its own FLIP.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it simpler, and I like that.

@dsainati1 dsainati1 requested a review from dete May 2, 2023 14:39
Copy link
Contributor

@bjartek bjartek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been busy so have not had time to look at this or follow this much at all.

Is cadence supposed to be simple and readable?

Some of these advanced entitlement mapping are not simple and not easy to understand. What is the added benefit here? I really second what @dete is saying here in that we need practical examples not abstract ones.

r.foo()
let ref: auth(E) &R = &r
ref.foo()
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it simpler, and I like that.

A single member definition can include multiple entitlements, using either a `|` or a `,` separator when defining the list.

An entitlement list defined using a `|` functions like a disjunction (or an "or"); it is accessible to any `auth` reference with any of those entitlements.
An entitlement list defined using a `,` functions like a conjunction set (or an "and"); it is accessible only to an `auth` reference with all of those entitlements.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally i feel that || and && are more natural operators to use.

@dsainati1
Copy link
Contributor Author

dsainati1 commented May 3, 2023

Some of these advanced entitlement mapping are not simple and not easy to understand. What is the added benefit here? I really second what @dete is saying here in that we need practical examples not abstract ones.

I completely agree when the purpose of these examples is to teach users how the language works and tutorialize the new features for new users. When we write the documentation for entitlements, we should make sure that the examples we use are relevant to the kind of code smart contract developers actually write. We will also need to update the existing Cadence tutorial to use the new entitlements in their implementation, which I think will go a long way towards making this feature simple and comprehensible to people.

However, the purpose of this FLIP is not to explain how this feature is going to work for users, and should not be used as reference documentation after the feature is deployed. The FLIP is a reference document for the designers and implementors of the language, and needs to be precise and detailed in its language, and simple (to the point of abstraction) in its examples so that people who are debating the design and implementation of the feature have all the information they need to make decisions. This means that the FLIP is necessarily going to be more complex and more abstract than the actual documentation will be, and I wouldn't use this FLIP as a basis for what the documentation will look like when it's finished.

@austinkline
Copy link
Contributor

Hi everyone! Apologies as well for only just not commenting on this FLIP, I'll try to make a note to come back to these more often in the future but it has been hard to keep track of them lately. I wanted to summarize some points after our breakout session for Hybrid Custody Wednesday and a Design meeting yesterday (Thursday), and share some thoughts that I'm hoping we can clarify a path for so that the Hybrid Custody initiative can continue uninterrupted

  1. It was mentioned that Capabilities might be broken with this change and in that case would have to be reissued by the Account they belong to
  2. Entitlements could alter what an AuthAccount is which means stored types which use it could break. For instance, the working hybrid custody contract is storing the following type: Capability<&AuthAccount>, this type could become invalid.
  3. On the release of Entitlements, functions with the pub access modifier would be accessible to anyone with a reference to any restricted type of that object, existing assumptions on access control won't be true, and some functions will be vulnerable on existing deployed contracts

Some thoughts:

Breaking Capabilities

Breaking Capabilities isn't really an option, not just for Hybrid Custody but for lots of products out there which store capabilities which they are not able to reissue. For instance, if we broken capabilities, all storefront listings across all products (TopShot, All Day, Flowty, Gaia, Find) would all break and only users would be able to fix them.

In general, I do not think we can consider any change which will break existing Capabilities. Cap Cons has an edge case I think we can live with (if at the time of migration, the capability's link is not valid, it will not be valid), but my understanding on Entitlements after hearing information about it in our calls this week is that they would all be invalid. It sounds like this is a tradeoff to prevent malicious access of public function modifiers but please correct me if I'm wrong.

Let's assume breaking capabilities is not an option, here. What other ways are there to handle this migration? I've seen it mentioned that you could make all capabilities that currently exist into auth capabilities but that seems like it would leak access to things we don't want folks to be able to reach. Is there anything else on that front which has been discussed?

AuthAccount type definitions

Account linking has been out for a while at this point (a month?). If we change the type definition of Accounts such that AuthAccount is not valid anymore, then all existing contracts which store this type like HybridCustody will break permanently (we cannot update existing type definitions)

What is the plan to move forward, there? What I don't want is a world where we have to pause all of our Hybrid Custody work because changes like this are in-flight. It could be months before this/stable cadence are out so waiting on them isn't a great option for us. Daniel mentioned some ability to make a type alias to keep AuthAccount for legacy reasons such as what I'm describing here, is that the plan? And if not, how would transaction prepare statements have to change and what's the plan to get all transactions updated? Especially for Dapper Wallet, that will be a huge challenge.

Pub access modifier

Based on the last two meetings described earlier, any functions with the pub access modifier would be callable by any borrowed reference to an underlying resource with entitlements, regardless of its restricted types. In the long-term, that is probably fine, but what is the path to get there? In my mind, even a single block where someone can submit a transaction with public access to something a contract didn't intent to expose is not acceptable. Is there a way to roll that specific piece of functionality out over time?

@bluesign mentioned the following in the hybrid custody discord channel as an option:

  • Deprecate pub
  • introduce entitlements and treat pub as some entitlement ( pub becomes access(pub) instead of access(all) )
  • remove pub

We don't want to throw a huge wrench in Entitlements or Hybrid Custody, and we also want to make sure that other products not related to either initiative don't break irreparably when this feature goes out. What would make the most sense in terms of tackling these challenges? We mentioned a breakout session in our last call, can we start there?

We're trying pretty hard to keep momentum on the Hybrid Custody front, so the more momentum we can maintain there the better

@dsainati1
Copy link
Contributor Author

Thanks for your thoughts @austinkline, these are great. I'll address them in reverse order (from least to most complex).

Pub access modifier

I just published #84, which proposes that we remove the pub (and priv) access modifiers from the language, requiring users to use the long form access(all) and access(self). This would take effect with the release of Stable Cadence, essentially requiring that all contracts need to be updated to conform to the new Stable Cadence changes before they can be used again, and there would not be any block where data is unduly exposed to attack that was previously safe.

AuthAccount type definitions

Based on what you've outlined here I think it makes sense to leave &AuthAccount as an existing type alias for the fully entitled &Account reference, and &PublicAccount as a type alias for the unentitled reference. This would maintain the old behavior (i.e. you are able to do everything on an account or nothing), but if users want more granular control (e.g. only publishing new contracts and nothing else) they would need to use the new entitlements feature. We should include this in the FLIP for AuthAccount Capabilities when it gets published (@turbolent).

Breaking Capabilities

In the above point, we would be able to define &AuthAccount to be a fully entitled reference to Account for 2 reasons:

  1. The binary nature of the authorization as explained above, either you have full permission or you don't. There are no existing account references that have a partial authorization to the account.

  2. We would know a priori what all the possible entitlements on an Account will be, because we control the Account type, and thus can create an alias &AuthAccount = auth(ContractUpdate, ContractAdd, KeyAdd, ... etc) &Account since we know all the possible entitlements that an account would need.

These two assumptions do not hold in general, which is what makes it very complicated to figure out how to migrate an existing Capability<&{X}> reference. In the old old model of entitlements, when entitlements were interfaces, we could transform any reference &{X} into an auth(X) &{X}, as this would let the reference call all the functions on X but not anything more, which matches the old behavior of restricted types. However, now that entitlements are independent of interfaces, this isn't possible just from inspecting the type syntactically.

We are going to have a breakout session to discuss this further (please attend if you are interested in this topic!), because I agree that we can't just break all existing capabilities, so we need to come up with a migration strategy. One potential option would be to allow contract upgrades to be scheduled in advance, and use the entitlements added in those prescheduled upgrades to give us the answer to point 2 above, which would allow us to migrate references of type &{X} to auth(E1, E2, ...) &{X}, where E1 through En would be all the entitlements that exist on X. However as you point out this relies on developers getting the upgrade "right" on the first try, which may be an unrealistic assumption to make.

@bjartek
Copy link
Contributor

bjartek commented May 5, 2023

I just published #84, which proposes that we remove the pub (and priv) access modifiers from the language, requiring users to use the long form access(all) and access(self). This would take effect with the release of Stable Cadence, essentially requiring that all contracts need to be updated to conform to the new Stable Cadence changes before they can be used again, and there would not be any block where data is unduly exposed to attack that was previously safe.

When is entitlements comming out then? Because that cannot happen at the same time as this. One has to come first to make people safe and then we can fix things with entitlments later. We have to do this in multiple steps even though i hate breaking backwards compatability often.

So in my eyes it looks like stable cadence will have to atleast be a double rocket. First we launch prelimiary changes, everybody have to fix things, then we do another change and people have to fix again and then we are flying to the moon?

@dsainati1
Copy link
Contributor Author

@bjartek Changing pub to access(all) does not inherently make a contract safer, the two mean the same thing one is just an alias for the other. The benefit of packaging this change with entitlements is that it will essentially break all contracts. Then, when entitlements are rolled out as part of Stable Cadence, all these contracts that become unsafe will also stop type checking, making them uninteractable until they are updated to fix the new type errors (and also to use entitlements, hence making them safe again).

@bjartek
Copy link
Contributor

bjartek commented May 5, 2023

@bjartek Changing pub to access(all) does not inherently make a contract safer, the two mean the same thing one is just an alias for the other. The benefit of packaging this change with entitlements is that it will essentially break all contracts. Then, when entitlements are rolled out as part of Stable Cadence, all these contracts that become unsafe will also stop type checking, making them uninteractable until they are updated to fix the new type errors (and also to use entitlements, hence making them safe again).

Ok, that is better then them working and beeing exploitable atleast.

@turbolent
Copy link
Member

@austinkline Thank you for the great feedback, questions, and concerns! We will work through these points in the coming weeks.

In the meantime, please do not consider this FLIP or the current lack of answers for your points be any blocker for any work that you have in progress.

@austinkline
Copy link
Contributor

@austinkline Thank you for the great feedback, questions, and concerns! We will work through these points in the coming weeks.

In the meantime, please do not consider this FLIP or the current lack of answers for your points be any blocker for any work that you have in progress.

Thanks @turbolent and @dsainati1! I'll keep an eye out for follow-up discussion. Please feel free to loop me in if I can assist in any way

Copy link
Member

@SupunS SupunS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple of minor nitpicks:

cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved
cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved
cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved
cadence/2022-12-14-auth-remodel.md Outdated Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file name should be 20221214-...

Copy link
Member

@turbolent turbolent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@dsainati1 dsainati1 dismissed dete’s stale review May 23, 2023 23:28

Spoke with Dete offline about his concerns and addressed them.

@dsainati1 dsainati1 merged commit 91860db into main May 23, 2023
@dsainati1 dsainati1 deleted the sainati/auth-fields branch May 23, 2023 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.