From 15865cdb12ae54468279296df0fdfdf31f65abec Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 14 Dec 2022 15:13:08 -0500 Subject: [PATCH 01/30] beginning of FLIP --- cadence/2022-12-14-auth-remodel.md | 256 +++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 cadence/2022-12-14-auth-remodel.md diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md new file mode 100644 index 00000000..3e7e4b72 --- /dev/null +++ b/cadence/2022-12-14-auth-remodel.md @@ -0,0 +1,256 @@ +--- +status: draft +flip: NNN (do not set) +authors: Daniel Sainati (daniel.sainati@dapperlabs.com) +sponsor: Daniel Sainati (daniel.sainati@dapperlabs.com) +updated: 2022-12-14 +--- + +# `auth` model changes + +## Objective + +This FLIP proposes two major changes to the language: +1) The addition of a new access modifier on contracts, structs and resources: `auth` +2) A change to the behavior of reference types to add granular authority restrictions to specific interfaces, and allow safe downcasting + +## Motivation + +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 +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 +with a `&{Balance}` reference would not be able to cast that reference to a `&{Receiver}` reference, despite the fact +that there would be no safety concerns in doing so; it should be possible to call `deposit` regardless of what kind of +reference a user possesses. + +The proposed change is designed to overcome this limitation by allowing developers to directly mark which fields and functions +are dangerous and should be access-limited (using the new `auth` keyword), and which should be generally available to anyone. + +## User Benefit + +How will users (or other contributors) benefit from this work? What would be the +headline in the release notes or blog post? + +## Design Proposal + +### `auth` fields + +The first portion of this FLIP proposes to add a new access control modifier to field and function declarations in composite types: +`access(auth)`, which allows access to either the immediate owner of the resource (i.e. anybody who has the actual resource value), +or someone with an `auth` reference to the type on which the member is defined. + +Like `access(contract)` and `access(account)`, this new modifier sits exactly between `pub` and `priv` (or equivalently `access(self)`) +in permissiveness; it allows less access than `pub`, but strictly more than `priv`, as an `auth` field or function can be used anywhere +in the implementation of the composite. To see why, consider that `self` is necessarily of the same type as the composite, meaning +that the access rules defined above allow any `auth` members to be accessed on it. + +As such, the following would be prohibited statically: + +```cadence +pub resource R { + access(auth) fun foo() { ... } +} +let r: &R = // ... +r.foo() +``` + +while all of these would be permitted: + +```cadence +pub resource R { + access(auth) fun foo() { ... } + pub fun bar() { + self.foo() + } +} +let r: @R = // ... +r.foo() +let ref: auth &R = &r +ref.foo() +``` + +### Safely Downcastable References + +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 +in the curly braces denote the interface types to which that reference has `auth` acccess. This permits these references +to access `auth` members on the interfaces to which they have `auth` access. So, for example, given three interface definitions and +a composite definition: + +```cadecnce +pub resource interface A { + access(auth) fun foo() +} +pub resource interface B { + access(auth) fun bar() +} +pub resource interface C { + pub fun baz() +} +pub resource R: A, B, C { + // ... +} +``` + +a value of type `auth{A} &R` would be able to access `foo`, because the reference has `auth` access to `A`, but not `bar`, because +it does not have `auth` access to `B`. However, `baz` would be accessible on this reference, since it has `pub` access. + +The interfaces over which a reference can be authorized must be a supertypes of the underlying referenced type: it is nonsensical, +for example, to express a type like `auth{A} &{B}`, since `A` and `B` are disjoint interfaces that do not share a heirarchy, and thus +the `auth` access to `A` granted by this reference would have no effect. As such, given a list of `I`s in a reference `auth{I1, I2} &T`, +we require that `I` appear in the conformance heirarchy of `T` if it is a composite type, or that `I` exists in the restriction set of `T` +if it is a restricted type. + +The other part of this change is to remove the limitations on resource downcasting that used to exist. Prior to this change, +non-`auth` references could not be downcast at all, since the sole purpose of the `auth` keyword was to indicate that references could be +downcast. With the proposed change, all reference types can be downcast or upcast the same way any other type would be. So, for example +`&{A} as! &R` would be valid, as would `&AnyResource as? &{B, C}` or `&{A} as? &{B}`, using the hierarchy defined above. + +However, the `auth`-ness (and the set of interfaces for which the reference is `auth`) would not change on downcasting, nor would that set +be expandable via casting. The subtyping rules for `auth` references is that `auth {U1, U2, ... } &X <: auth {T1, T2, ... } &X` whenever `{U1, U2, ... }` +is a superset of `{T1, T2, ... }`, or equivalently `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, T = U`. + +As such, `auth{A, B} &R` would be statically upcastable to `auth{A} &R`, since this decreases the permissions on the +reference, it would require a runtime cast to go from `auth{A} &R` to `auth{A, B} &R`, as this cast would only succeed if the underlying +runtime value was `auth` for both `A` and `B`. So in the code below: + +```cadence +fun foo(ref: &R): Bool { + let authRef = ref as? auth{A, B} &R + return authRef != nil +} +let r <- create R() +let ref1 = &r as auth{A} &R +let ref2 = &r as auth{A, B, C} &R +`` + +`foo` would return `true` when called with `ref2`, because the runtime type of `ref` in the failable cast is a subtype of `auth{A, B} &R`, since +`{A, B, C}` is a superset of `{A, B}`, but would return `false` when called with `ref1`, since `{A}` is not a superset of `{A, B}`. + +### Drawbacks + +Why should this *not* be done? What negative impact does it have? + +### Alternatives Considered + +* Make sure to discuss the relative merits of alternatives to your proposal. + +### Performance Implications + +* Do you expect any (speed / memory)? How will you confirm? +* There should be microbenchmarks. Are there? +* There should be end-to-end tests and benchmarks. If there are not +(since this is still a design), how will you track that these will be created? + +### Dependencies + +* Dependencies: does this proposal add any new dependencies to Flow? +* Dependent projects: are there other areas of Flow or things that use Flow +(Access API, Wallets, SDKs, etc.) that this affects? +How have you identified these dependencies and are you sure they are complete? +If there are dependencies, how are you managing those changes? + +### Engineering Impact + +* Do you expect changes to binary size / build time / test times? +* Who will maintain this code? Is this code in its own buildable unit? +Can this code be tested in its own? +Is visibility suitably restricted to only a small API surface for others to use? + +### Best Practices + +* Does this proposal change best practices for some aspect of using/developing Flow? +How will these changes be communicated/enforced? + +### Tutorials and Examples + +With this new design, the `Vault` heirarchy might be written like so: + +```cadence +pub resource interface Provider { + access(auth) fun withdraw(amount: UFix64): @Vault { + // ... + } +} + +pub resource interface Receiver { + pub fun deposit(from: @Vault) { + // ... + } +} + +pub resource interface Balance { + pub var balance: UFix64 +} + +pub resource Vault: Provider, Receiver, Balance { + access(auth) fun withdraw(amount: UFix64): @Vault { + // ... + } + pub fun deposit(from: @Vault) { + // ... + } + pub var balance: UFix64 +} +``` + +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`. + +### Compatibility + +This would not be backwards compatible with existing code, and thus would need to be part of the Stable Cadence release. +Most contracts would need to be audited and rewritten to use the new `auth` access modifier, as they would immediately become +vulnerable to having `pub` fields accessed from now-downcastable references. + +### User Impact + +* What are the user-facing changes? How will this feature be rolled out? + +## Related Issues + +What related issues do you consider out of scope for this proposal, +but could be addressed independently in the future? + +## Prior Art + +Does the proposed idea/feature exist in other systems and +what experience has their community had? + +This section is intended to encourage you as an author to think about the +lessons learned from other projects and provide readers of the proposal +with a fuller picture. + +It's fine if there is no prior art; your ideas are interesting regardless of +whether or not they are based on existing work. + +## Questions and Discussion Topics + +* The upcoming proposed changes to permit interfaces to conform to other interfaces will necessarily change the subtyping rules +for `auth` refrences, as it would no longer be sufficient to compare the sets for the two `auth` references just based on membership. +The membership check would still exist, but it would function as a width subtyping rule, in addition to a depth rule based on +interface chains. I.e. it would be the case that for two auth references, `auth{U} &X <: auth{T1} &X`, if `U <: T`. + +We can combine this depth rule with the already existing width rule to yield a combined subtyping rule: `auth{U1, U2, ... } &X <: auth{T1, T2, ... } &X`, +whenever `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, T <: U`. + +So, for example, given an interface heirarchy: + +```cadence +pub resource interface A {} +pub resource interface B {} +pub resource interface C: A {} +pub resource interface D: B {} +pub resource interface E: C, D {} +pub resource R: E {} +``` + +we would have `auth{E} &R <: auth{C} &R <: auth{A} &R <: &R` and `auth{E} &R <: auth{D} &R <: auth{B} &R <: &R`. +It would also be the case that `auth{D, C} &R <: auth{A} &R` as well, and that `auth{E} &R <: auth{B, C} &R`. \ No newline at end of file From 7d033d495214a1b240868a2cc12c1d02a94604b6 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 15 Dec 2022 15:21:42 -0500 Subject: [PATCH 02/30] add more details --- cadence/2022-12-14-auth-remodel.md | 151 +++++++++++++++++++++-------- 1 file changed, 111 insertions(+), 40 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 3e7e4b72..476b7cd8 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -32,8 +32,16 @@ are dangerous and should be access-limited (using the new `auth` keyword), and w ## User Benefit -How will users (or other contributors) benefit from this work? What would be the -headline in the release notes or blog post? +This feature would make interacting with references significantly easier, as users would no longer be restricted from +accessing methods like `deposit` that are safe for anyone to use, just because they do not possess a reference of the specific type, +or because they are trying to write a generic method that works, say, on any `NFT` type. + +It would also increase safety, as we would be able to do additional checking on the values and types to which users create capabilities. +For example, it is currently possible for a user to (presumably accidentally) create a public Capability directly to a `Vault` object, +rather than a Capability to a `&{Balance, Receiver}` restricted type as is intended. With these changes, such a Capability still would +not be subject to having its funds `withdraw`n, as this `Vault` Capability would not be `auth`. In order to have access to an `auth` +method like `withdraw`, the user would need to explicitly create the Capability with an `auth` reference, and we could enforce statically +(or warn) that `auth` reference capabilies should not be stored publicly. ## Design Proposal @@ -127,45 +135,24 @@ fun foo(ref: &R): Bool { let r <- create R() let ref1 = &r as auth{A} &R let ref2 = &r as auth{A, B, C} &R -`` +``` `foo` would return `true` when called with `ref2`, because the runtime type of `ref` in the failable cast is a subtype of `auth{A, B} &R`, since `{A, B, C}` is a superset of `{A, B}`, but would return `false` when called with `ref1`, since `{A}` is not a superset of `{A, B}`. -### Drawbacks - -Why should this *not* be done? What negative impact does it have? - -### Alternatives Considered - -* Make sure to discuss the relative merits of alternatives to your proposal. - -### Performance Implications - -* Do you expect any (speed / memory)? How will you confirm? -* There should be microbenchmarks. Are there? -* There should be end-to-end tests and benchmarks. If there are not -(since this is still a design), how will you track that these will be created? +Because only interface types can appear in the curly braces after the `auth` keyword, the `auth` keyword without a restriction set will denote +full (unrestricted) `auth` access to the reference type. E.g. `auth &R` would be a reference with full `auth` access to `R`. -### Dependencies - -* Dependencies: does this proposal add any new dependencies to Flow? -* Dependent projects: are there other areas of Flow or things that use Flow -(Access API, Wallets, SDKs, etc.) that this affects? -How have you identified these dependencies and are you sure they are complete? -If there are dependencies, how are you managing those changes? - -### Engineering Impact +### Drawbacks -* Do you expect changes to binary size / build time / test times? -* Who will maintain this code? Is this code in its own buildable unit? -Can this code be tested in its own? -Is visibility suitably restricted to only a small API surface for others to use? +This dramatically increases the complexity of references and capabilities by requiring users to think not only about which types they +are creating the references with, but also which `auth` accesses they are providing on each reference they create. It also increases +the implementation complexity of the type system significantly by adding an additional dimension to the reference type's subtype heirarchy. ### Best Practices -* Does this proposal change best practices for some aspect of using/developing Flow? -How will these changes be communicated/enforced? +This would encourage users to use the new `auth` access modifier to restrict access on their contracts and resources; and use different +`auth` restriction sets to control access. ### Tutorials and Examples @@ -212,12 +199,10 @@ vulnerable to having `pub` fields accessed from now-downcastable references. ### User Impact -* What are the user-facing changes? How will this feature be rolled out? - -## Related Issues - -What related issues do you consider out of scope for this proposal, -but could be addressed independently in the future? +There is a huge user impact to rolling out this change; every single contract would become invalid, and would need to be manually audited +by the authors in order to be used. This is because all code previously written using the old model of access control (wherein giving someone +a `&{Balance}` would prevent them from calling `pub` functions on `Provider` like `withdraw`), would become invalidated, allowing everyone +to call `pub` members like `withdraw` unless those methods were updated to be `auth`. ## Prior Art @@ -239,7 +224,7 @@ The membership check would still exist, but it would function as a width subtypi interface chains. I.e. it would be the case that for two auth references, `auth{U} &X <: auth{T1} &X`, if `U <: T`. We can combine this depth rule with the already existing width rule to yield a combined subtyping rule: `auth{U1, U2, ... } &X <: auth{T1, T2, ... } &X`, -whenever `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, T <: U`. +whenever `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, U <: T`. So, for example, given an interface heirarchy: @@ -253,4 +238,90 @@ pub resource R: E {} ``` we would have `auth{E} &R <: auth{C} &R <: auth{A} &R <: &R` and `auth{E} &R <: auth{D} &R <: auth{B} &R <: &R`. -It would also be the case that `auth{D, C} &R <: auth{A} &R` as well, and that `auth{E} &R <: auth{B, C} &R`. \ No newline at end of file +It would also be the case that `auth{D, C} &R <: auth{A} &R` as well, because `C <: A`, and that `auth{E} &R <: auth{B, C} &R`, +beacuse `E <: B` and `E <: C`. + +* It is unclear how this should interact with the new attachments feature. Prior to this proposal a functional mental model +for attachments was to treat them as implicit type-disambiguated `pub` fields on the resource or struct to which they were attached. +The existing behavior preventing downcasting of non-`auth` references would prevent someone from accessing a `Vault`-attachment (which +would have access to functions like `withdraw` via the `base` reference) if they only possessed a `&{Balance}`, since only `Balance`-attachments +would be visible with such a reference. + +One simple approach would be to allow attachments to be accessed only on values that have `auth` access to the attachment's `base` type. +I.e. given an `attachment A for I`, `v[A]` would be permissable when `v` has type `auth{I} &R` but not when `v` is just a regular `&R`, +or even an `&{I}`. Functionally this would mean that attachments would become `auth` fields in the mental model described above. + +This approach is simple but very restrictive. In the motivating `KittyHat` use case for example, only users with an `auth`-reference +to the `Kitty` resource would be able to use the `Hat`. This would either make the `KittyHat` almost unusable without giving out +an unreasonable amount of authority to users, or require the author of the `Kitty` to write a specific interface describing the set of +features they would want an attachment to be able to use. This latter case would completely defeat the point of attachments in the first place, +since they are designed to be usable with no prior planning from the base value's author. + +An alternative would be to implicitly parameterize the attachment's `auth` access over the `auth` access of its base value. In this model, +attachments would remain `pub` accessible, but would themselves permit the declaration of `auth` members. The `base` value, +which currently is simply a `&R` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have it's `auth`-ness +depend on the `auth`-ness of the member function in question. In a member declaration `access(auth) fun foo()` in `A`, the `base` variable would have +type `auth &R`, while in a member declaration in `A` `pub fun bar()`, `base` would just be an `&R`. This would effectively mean that the `auth`-access +members of the `base` would only be available to the attachment author in `auth`-access members on the attachment. Similarly, in an `auth`-access +member, the `self` reference of the attachment `A` would be `auth &A`, while in a `pub`-access member it would just be `&A`. + +This would then be combined with a change to the attachment access rules: rather than `v[A]` always returning an `&A?` value, the type of the returned +attachment reference would depend on the type of `v`. If `v` is not a reference, or a reference with `auth` access to `A`'s `base` type, then `v[A]` would return +an `(auth &A)?` typed, while if the reference did not have access to `A`'s `base`, then the access would be a regular `&A?` type. This would prevent +the attachment from accessing `auth` members on its `base` unless the specific instance of that base to which it is attached has the proper `auth` access. + +So, for example, given the following declaration: +```cadence +attachment CurrencyConverter for Provider { + pub fun convert(_ amount: UFix64): UFix64 { + // ... + } + + pub fun convertVault(_ vault: @Vault): @Vault { + vault.balance = self.convert(vault.balance) + return <-vault + } + + access(auth) fun withdraw(_ amount: UFix64): @Vault { + let convertedAmount = self.convert(amount) + // this is permitted because this function has `auth` access + // on the attachment, and thus `base` has type `auth{Provider} &{Provider}` + return <-base.withdraw(amount: amount) + } + + pub fun deposit (from: @Vault) { + // cast is permissable under the new reference casting rules + let baseReceiverOptional = base as? &{Receiver} + if let baseReceiver = baseReceiverOptional { + let convertedVault <- self.convertVault(<-from) + // this is ok because `deposit` has `pub` access + baseReceiver.deposit(from: <-convertedVault) + } + } + + pub fun maliciousStealingFunctionA(_ amount: UFix64): @Vault { + // This fails statically, as `self` here is just an `&A` + // because `maliciousStealingFunctionA`'s access is `pub`, + // and therefore `self` does not have access to `withdraw` + return <-self.withdraw(amount) + } + + pub fun maliciousStealingFunctionB(_ amount: UFix64): @Vault { + // This fails statically, as `base` here is just an `&{Provider}` + // because `maliciousStealingFunctionB`'s access is `pub`, + // and therefore `base` does not have access to `withdraw` + return <-base.withdraw(amount: amount) + } +} + +let vault <- attach CurrencyConverter() to <-create Vault(balance: /*...*/) +let authVaultReference = &vault as auth{Provider} &Vault +let converterRef = authVaultReference[CurrencyConverter]! // has type auth &CurrencyConverter, can call `withdraw` + +let otherVaultReference = &vault as auth{Balance} &Vault +let otheConverterRef = otherVaultReference[CurrencyConverter]! // has type &CurrencyConverter, cannot call `withdraw` +``` + +* One potential change this unlocks would be to restrict the creation of Capabilities based on the `auth`-ness of the reference +they contain. We could restrict the `public` domain to be only for non-`auth` Capabilities, while the `private` domain would be only +for `auth`-references, which would prevent accidentally allowing anybody to get access to your `withdraw` function, for example. \ No newline at end of file From 73b07577da682b3b2c317a1b8e9a6a07e04e7d8c Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 16 Dec 2022 10:41:19 -0500 Subject: [PATCH 03/30] add section on interface inheritance --- cadence/2022-12-14-auth-remodel.md | 44 +++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 476b7cd8..fffaa3dd 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -3,7 +3,7 @@ status: draft flip: NNN (do not set) authors: Daniel Sainati (daniel.sainati@dapperlabs.com) sponsor: Daniel Sainati (daniel.sainati@dapperlabs.com) -updated: 2022-12-14 +updated: 2022-12-16 --- # `auth` model changes @@ -81,6 +81,38 @@ let ref: auth &R = &r ref.foo() ``` +Note also that the interface implementation rules allow a composite to implement an `access(auth)` interface member with a `pub` +composite member, as this is less restrictive, but not the other way around, as this would allow the interface type to gain more access +to the composite than should be possible. So this would be acceptable: + +```cadence +pub resource interface I { + access(auth) fun foo() +} + +pub resource R: I { + pub fun foo() {} +} +``` + +since upcasting a value of type `&R` to type `&{I}` would not allow the reference to call any more functions. There is no similar concern +with downcasting a `&{I}` to a `&R` and gaining the ability to call `foo`, because `foo` is declared as `pub` on `R`, and we treat the +access control declarations on the concrete composite as the "source of truth" for the value's access control at runtime. + +while this is not: + +```cadence +pub resource interface I { + pub fun foo() +} + +pub resource R: I { + access(auth) fun foo() {} +} +``` + +since if this were to typecheck, anybody with a `&R` reference could upcast it to `&{I}` and thus gain the ability to call `foo`. + ### Safely Downcastable References The second, more complex part of this proposal, is a change to the behavior of references to allow the `auth` modifier not to @@ -206,15 +238,7 @@ to call `pub` members like `withdraw` unless those methods were updated to be `a ## Prior Art -Does the proposed idea/feature exist in other systems and -what experience has their community had? - -This section is intended to encourage you as an author to think about the -lessons learned from other projects and provide readers of the proposal -with a fuller picture. - -It's fine if there is no prior art; your ideas are interesting regardless of -whether or not they are based on existing work. +* This section needs filling out; would love to know if there are any languages out there doing something similar to this. ## Questions and Discussion Topics From be521b93a128e7caaba8e6a54013573439589dab Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 16 Dec 2022 10:45:19 -0500 Subject: [PATCH 04/30] fix formatting --- cadence/2022-12-14-auth-remodel.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index fffaa3dd..4f511751 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -247,23 +247,23 @@ for `auth` refrences, as it would no longer be sufficient to compare the sets fo The membership check would still exist, but it would function as a width subtyping rule, in addition to a depth rule based on interface chains. I.e. it would be the case that for two auth references, `auth{U} &X <: auth{T1} &X`, if `U <: T`. -We can combine this depth rule with the already existing width rule to yield a combined subtyping rule: `auth{U1, U2, ... } &X <: auth{T1, T2, ... } &X`, + We can combine this depth rule with the already existing width rule to yield a combined subtyping rule: `auth{U1, U2, ... } &X <: auth{T1, T2, ... } &X`, whenever `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, U <: T`. -So, for example, given an interface heirarchy: + So, for example, given an interface heirarchy: -```cadence -pub resource interface A {} -pub resource interface B {} -pub resource interface C: A {} -pub resource interface D: B {} -pub resource interface E: C, D {} -pub resource R: E {} -``` + ```cadence + pub resource interface A {} + pub resource interface B {} + pub resource interface C: A {} + pub resource interface D: B {} + pub resource interface E: C, D {} + pub resource R: E {} + ``` -we would have `auth{E} &R <: auth{C} &R <: auth{A} &R <: &R` and `auth{E} &R <: auth{D} &R <: auth{B} &R <: &R`. -It would also be the case that `auth{D, C} &R <: auth{A} &R` as well, because `C <: A`, and that `auth{E} &R <: auth{B, C} &R`, -beacuse `E <: B` and `E <: C`. + we would have `auth{E} &R <: auth{C} &R <: auth{A} &R <: &R` and `auth{E} &R <: auth{D} &R <: auth{B} &R <: &R`. + It would also be the case that `auth{D, C} &R <: auth{A} &R` as well, because `C <: A`, and that `auth{E} &R <: auth{B, C} &R`, + beacuse `E <: B` and `E <: C`. * It is unclear how this should interact with the new attachments feature. Prior to this proposal a functional mental model for attachments was to treat them as implicit type-disambiguated `pub` fields on the resource or struct to which they were attached. From 74551c77bceec0d03db670f58bf3dcaff4b8cdc9 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 16 Dec 2022 10:46:19 -0500 Subject: [PATCH 05/30] fix formatting --- cadence/2022-12-14-auth-remodel.md | 130 ++++++++++++++--------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 4f511751..14b44181 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -271,80 +271,80 @@ The existing behavior preventing downcasting of non-`auth` references would prev would have access to functions like `withdraw` via the `base` reference) if they only possessed a `&{Balance}`, since only `Balance`-attachments would be visible with such a reference. -One simple approach would be to allow attachments to be accessed only on values that have `auth` access to the attachment's `base` type. -I.e. given an `attachment A for I`, `v[A]` would be permissable when `v` has type `auth{I} &R` but not when `v` is just a regular `&R`, -or even an `&{I}`. Functionally this would mean that attachments would become `auth` fields in the mental model described above. - -This approach is simple but very restrictive. In the motivating `KittyHat` use case for example, only users with an `auth`-reference -to the `Kitty` resource would be able to use the `Hat`. This would either make the `KittyHat` almost unusable without giving out -an unreasonable amount of authority to users, or require the author of the `Kitty` to write a specific interface describing the set of -features they would want an attachment to be able to use. This latter case would completely defeat the point of attachments in the first place, -since they are designed to be usable with no prior planning from the base value's author. - -An alternative would be to implicitly parameterize the attachment's `auth` access over the `auth` access of its base value. In this model, -attachments would remain `pub` accessible, but would themselves permit the declaration of `auth` members. The `base` value, -which currently is simply a `&R` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have it's `auth`-ness -depend on the `auth`-ness of the member function in question. In a member declaration `access(auth) fun foo()` in `A`, the `base` variable would have -type `auth &R`, while in a member declaration in `A` `pub fun bar()`, `base` would just be an `&R`. This would effectively mean that the `auth`-access -members of the `base` would only be available to the attachment author in `auth`-access members on the attachment. Similarly, in an `auth`-access -member, the `self` reference of the attachment `A` would be `auth &A`, while in a `pub`-access member it would just be `&A`. - -This would then be combined with a change to the attachment access rules: rather than `v[A]` always returning an `&A?` value, the type of the returned -attachment reference would depend on the type of `v`. If `v` is not a reference, or a reference with `auth` access to `A`'s `base` type, then `v[A]` would return -an `(auth &A)?` typed, while if the reference did not have access to `A`'s `base`, then the access would be a regular `&A?` type. This would prevent -the attachment from accessing `auth` members on its `base` unless the specific instance of that base to which it is attached has the proper `auth` access. - -So, for example, given the following declaration: -```cadence -attachment CurrencyConverter for Provider { - pub fun convert(_ amount: UFix64): UFix64 { - // ... - } + One simple approach would be to allow attachments to be accessed only on values that have `auth` access to the attachment's `base` type. + I.e. given an `attachment A for I`, `v[A]` would be permissable when `v` has type `auth{I} &R` but not when `v` is just a regular `&R`, + or even an `&{I}`. Functionally this would mean that attachments would become `auth` fields in the mental model described above. + + This approach is simple but very restrictive. In the motivating `KittyHat` use case for example, only users with an `auth`-reference + to the `Kitty` resource would be able to use the `Hat`. This would either make the `KittyHat` almost unusable without giving out + an unreasonable amount of authority to users, or require the author of the `Kitty` to write a specific interface describing the set of + features they would want an attachment to be able to use. This latter case would completely defeat the point of attachments in the first place, + since they are designed to be usable with no prior planning from the base value's author. + + An alternative would be to implicitly parameterize the attachment's `auth` access over the `auth` access of its base value. In this model, + attachments would remain `pub` accessible, but would themselves permit the declaration of `auth` members. The `base` value, + which currently is simply a `&R` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have it's `auth`-ness + depend on the `auth`-ness of the member function in question. In a member declaration `access(auth) fun foo()` in `A`, the `base` variable would have + type `auth &R`, while in a member declaration in `A` `pub fun bar()`, `base` would just be an `&R`. This would effectively mean that the `auth`-access + members of the `base` would only be available to the attachment author in `auth`-access members on the attachment. Similarly, in an `auth`-access + member, the `self` reference of the attachment `A` would be `auth &A`, while in a `pub`-access member it would just be `&A`. + + This would then be combined with a change to the attachment access rules: rather than `v[A]` always returning an `&A?` value, the type of the returned + attachment reference would depend on the type of `v`. If `v` is not a reference, or a reference with `auth` access to `A`'s `base` type, then `v[A]` would return + an `(auth &A)?` typed, while if the reference did not have access to `A`'s `base`, then the access would be a regular `&A?` type. This would prevent + the attachment from accessing `auth` members on its `base` unless the specific instance of that base to which it is attached has the proper `auth` access. + + So, for example, given the following declaration: + ```cadence + attachment CurrencyConverter for Provider { + pub fun convert(_ amount: UFix64): UFix64 { + // ... + } - pub fun convertVault(_ vault: @Vault): @Vault { - vault.balance = self.convert(vault.balance) - return <-vault - } + pub fun convertVault(_ vault: @Vault): @Vault { + vault.balance = self.convert(vault.balance) + return <-vault + } - access(auth) fun withdraw(_ amount: UFix64): @Vault { - let convertedAmount = self.convert(amount) - // this is permitted because this function has `auth` access - // on the attachment, and thus `base` has type `auth{Provider} &{Provider}` - return <-base.withdraw(amount: amount) - } + access(auth) fun withdraw(_ amount: UFix64): @Vault { + let convertedAmount = self.convert(amount) + // this is permitted because this function has `auth` access + // on the attachment, and thus `base` has type `auth{Provider} &{Provider}` + return <-base.withdraw(amount: amount) + } - pub fun deposit (from: @Vault) { - // cast is permissable under the new reference casting rules - let baseReceiverOptional = base as? &{Receiver} - if let baseReceiver = baseReceiverOptional { - let convertedVault <- self.convertVault(<-from) - // this is ok because `deposit` has `pub` access - baseReceiver.deposit(from: <-convertedVault) + pub fun deposit (from: @Vault) { + // cast is permissable under the new reference casting rules + let baseReceiverOptional = base as? &{Receiver} + if let baseReceiver = baseReceiverOptional { + let convertedVault <- self.convertVault(<-from) + // this is ok because `deposit` has `pub` access + baseReceiver.deposit(from: <-convertedVault) + } } - } - pub fun maliciousStealingFunctionA(_ amount: UFix64): @Vault { - // This fails statically, as `self` here is just an `&A` - // because `maliciousStealingFunctionA`'s access is `pub`, - // and therefore `self` does not have access to `withdraw` - return <-self.withdraw(amount) - } + pub fun maliciousStealingFunctionA(_ amount: UFix64): @Vault { + // This fails statically, as `self` here is just an `&A` + // because `maliciousStealingFunctionA`'s access is `pub`, + // and therefore `self` does not have access to `withdraw` + return <-self.withdraw(amount) + } - pub fun maliciousStealingFunctionB(_ amount: UFix64): @Vault { - // This fails statically, as `base` here is just an `&{Provider}` - // because `maliciousStealingFunctionB`'s access is `pub`, - // and therefore `base` does not have access to `withdraw` - return <-base.withdraw(amount: amount) + pub fun maliciousStealingFunctionB(_ amount: UFix64): @Vault { + // This fails statically, as `base` here is just an `&{Provider}` + // because `maliciousStealingFunctionB`'s access is `pub`, + // and therefore `base` does not have access to `withdraw` + return <-base.withdraw(amount: amount) + } } -} -let vault <- attach CurrencyConverter() to <-create Vault(balance: /*...*/) -let authVaultReference = &vault as auth{Provider} &Vault -let converterRef = authVaultReference[CurrencyConverter]! // has type auth &CurrencyConverter, can call `withdraw` + let vault <- attach CurrencyConverter() to <-create Vault(balance: /*...*/) + let authVaultReference = &vault as auth{Provider} &Vault + let converterRef = authVaultReference[CurrencyConverter]! // has type auth &CurrencyConverter, can call `withdraw` -let otherVaultReference = &vault as auth{Balance} &Vault -let otheConverterRef = otherVaultReference[CurrencyConverter]! // has type &CurrencyConverter, cannot call `withdraw` -``` + let otherVaultReference = &vault as auth{Balance} &Vault + let otheConverterRef = otherVaultReference[CurrencyConverter]! // has type &CurrencyConverter, cannot call `withdraw` + ``` * One potential change this unlocks would be to restrict the creation of Capabilities based on the `auth`-ness of the reference they contain. We could restrict the `public` domain to be only for non-`auth` Capabilities, while the `private` domain would be only From 8a5cfa16f80ce90cb6299cc1fac7fbeeca7e8f00 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 16 Dec 2022 11:08:10 -0500 Subject: [PATCH 06/30] typo --- cadence/2022-12-14-auth-remodel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 14b44181..f5b5c629 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -291,7 +291,7 @@ would be visible with such a reference. This would then be combined with a change to the attachment access rules: rather than `v[A]` always returning an `&A?` value, the type of the returned attachment reference would depend on the type of `v`. If `v` is not a reference, or a reference with `auth` access to `A`'s `base` type, then `v[A]` would return - an `(auth &A)?` typed, while if the reference did not have access to `A`'s `base`, then the access would be a regular `&A?` type. This would prevent + an `(auth &A)?` type, while if the reference did not have access to `A`'s `base`, then the access would be a regular `&A?` type. This would prevent the attachment from accessing `auth` members on its `base` unless the specific instance of that base to which it is attached has the proper `auth` access. So, for example, given the following declaration: From fd624227acb415099c02b6000cd05ede79fc993e Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 3 Jan 2023 16:34:53 -0500 Subject: [PATCH 07/30] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bastian Müller --- cadence/2022-12-14-auth-remodel.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 14b44181..de449092 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -118,7 +118,7 @@ since if this were to typecheck, anybody with a `&R` reference could upcast it t 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 -in the curly braces denote the interface types to which that reference has `auth` acccess. This permits these references +in the curly braces denote the interface types to which that reference has `auth` access. This permits these references to access `auth` members on the interfaces to which they have `auth` access. So, for example, given three interface definitions and a composite definition: @@ -219,7 +219,7 @@ pub resource Vault: Provider, Receiver, Balance { ``` 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 also be able to cast this to a `&Vault` reference, 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`. @@ -243,7 +243,7 @@ to call `pub` members like `withdraw` unless those methods were updated to be `a ## Questions and Discussion Topics * The upcoming proposed changes to permit interfaces to conform to other interfaces will necessarily change the subtyping rules -for `auth` refrences, as it would no longer be sufficient to compare the sets for the two `auth` references just based on membership. +for `auth` references, as it would no longer be sufficient to compare the sets for the two `auth` references just based on membership. The membership check would still exist, but it would function as a width subtyping rule, in addition to a depth rule based on interface chains. I.e. it would be the case that for two auth references, `auth{U} &X <: auth{T1} &X`, if `U <: T`. @@ -263,7 +263,7 @@ whenever `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, U <: T`. we would have `auth{E} &R <: auth{C} &R <: auth{A} &R <: &R` and `auth{E} &R <: auth{D} &R <: auth{B} &R <: &R`. It would also be the case that `auth{D, C} &R <: auth{A} &R` as well, because `C <: A`, and that `auth{E} &R <: auth{B, C} &R`, - beacuse `E <: B` and `E <: C`. + because `E <: B` and `E <: C`. * It is unclear how this should interact with the new attachments feature. Prior to this proposal a functional mental model for attachments was to treat them as implicit type-disambiguated `pub` fields on the resource or struct to which they were attached. @@ -272,7 +272,7 @@ would have access to functions like `withdraw` via the `base` reference) if they would be visible with such a reference. One simple approach would be to allow attachments to be accessed only on values that have `auth` access to the attachment's `base` type. - I.e. given an `attachment A for I`, `v[A]` would be permissable when `v` has type `auth{I} &R` but not when `v` is just a regular `&R`, + I.e. given an `attachment A for I`, `v[A]` would be permissible when `v` has type `auth{I} &R` but not when `v` is just a regular `&R`, or even an `&{I}`. Functionally this would mean that attachments would become `auth` fields in the mental model described above. This approach is simple but very restrictive. In the motivating `KittyHat` use case for example, only users with an `auth`-reference @@ -283,7 +283,7 @@ would be visible with such a reference. An alternative would be to implicitly parameterize the attachment's `auth` access over the `auth` access of its base value. In this model, attachments would remain `pub` accessible, but would themselves permit the declaration of `auth` members. The `base` value, - which currently is simply a `&R` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have it's `auth`-ness + which currently is simply a `&R` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have its `auth`-ness depend on the `auth`-ness of the member function in question. In a member declaration `access(auth) fun foo()` in `A`, the `base` variable would have type `auth &R`, while in a member declaration in `A` `pub fun bar()`, `base` would just be an `&R`. This would effectively mean that the `auth`-access members of the `base` would only be available to the attachment author in `auth`-access members on the attachment. Similarly, in an `auth`-access From 040d35f918688c211a6f9f1ef65883436eb48321 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 3 Jan 2023 16:43:51 -0500 Subject: [PATCH 08/30] respond to review --- cadence/2022-12-14-auth-remodel.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 7d4121e0..3cf0b540 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -28,7 +28,7 @@ that there would be no safety concerns in doing so; it should be possible to cal reference a user possesses. The proposed change is designed to overcome this limitation by allowing developers to directly mark which fields and functions -are dangerous and should be access-limited (using the new `auth` keyword), and which should be generally available to anyone. +should be access-limited (using the new `auth` keyword), and which should be generally available to anyone. ## User Benefit @@ -156,8 +156,8 @@ be expandable via casting. The subtyping rules for `auth` references is that `au is a superset of `{T1, T2, ... }`, or equivalently `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, T = U`. As such, `auth{A, B} &R` would be statically upcastable to `auth{A} &R`, since this decreases the permissions on the -reference, it would require a runtime cast to go from `auth{A} &R` to `auth{A, B} &R`, as this cast would only succeed if the underlying -runtime value was `auth` for both `A` and `B`. So in the code below: +reference, it would require a runtime cast to go from `auth{A} &R` to `auth{A, B} &R`, as this cast would only succeed if the +runtime type of the reference was `auth` for both `A` and `B`. So in the code below: ```cadence fun foo(ref: &R): Bool { From 85edfaa3dee58749c3b67d71eeb57421a25a2304 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 3 Jan 2023 17:01:29 -0500 Subject: [PATCH 09/30] add an alternatives considered section --- cadence/2022-12-14-auth-remodel.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 3cf0b540..59a28f20 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -240,6 +240,36 @@ to call `pub` members like `withdraw` unless those methods were updated to be `a * This section needs filling out; would love to know if there are any languages out there doing something similar to this. +## Alternatives Considered + +A previous version of this idea involved having the implementors of concrete types like `Vault` specify in their interface conformance list +which interfaces were `auth` for that concrete type. So, for example, one would write something like (using strawman syntax): + +```cadence +pub resource interface A { + pub fun foo() +} +pub resource interface B { + pub fun bar() +} +pub resource R: auth A, B { + pub fun foo() {} + pub fun bar() {} +} +``` + +Then, given a reference of type `&R`, only the functions and fields defined on `B` would be accessible, since the reference is non-`auth`. In +order to access the fields and functions on `A`, one would need an `auth` reference to `R`, since `A` was declared as `auth` in `R`'s conformances. + +The upside of this proposal was that there was a single decision point, in that the implementor of the concrete type needed to decide which interfaces +would be `auth` and which would be not, and the creators of the interfaces and the users of the concrete types would have no decisions to make other than +whether or not the reference they are creating should be `auth` or not. + +The drawback of this compared to the proposal in this FLIP is that it lacks granularity; an entire interface must be made `auth` or not, whereas in the +FLIP's proposal individual functions can be declared with `auth` access or `pub` access. This allows the creator of the interface (and then later, the +creator of the reference type) to exert more fine-grained control over what is accessible. By contrast, the implementor of the concrete type has little +to no say in the access-control on their value beyond selecting which interfaces they wish to conform to. + ## Questions and Discussion Topics * The upcoming proposed changes to permit interfaces to conform to other interfaces will necessarily change the subtyping rules From c92d088a3a27763581931a19b9e2d8101649ef6b Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 4 Jan 2023 10:11:47 -0500 Subject: [PATCH 10/30] clarify auth types are subtypes of non-auth references --- cadence/2022-12-14-auth-remodel.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 59a28f20..b36f3dad 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -153,7 +153,8 @@ downcast. With the proposed change, all reference types can be downcast or upcas However, the `auth`-ness (and the set of interfaces for which the reference is `auth`) would not change on downcasting, nor would that set be expandable via casting. The subtyping rules for `auth` references is that `auth {U1, U2, ... } &X <: auth {T1, T2, ... } &X` whenever `{U1, U2, ... }` -is a superset of `{T1, T2, ... }`, or equivalently `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, T = U`. +is a superset of `{T1, T2, ... }`, or equivalently `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, T = U`. Of course, all `auth` reference types +would remain subtypes of all non-`auth` reference types as before. As such, `auth{A, B} &R` would be statically upcastable to `auth{A} &R`, since this decreases the permissions on the reference, it would require a runtime cast to go from `auth{A} &R` to `auth{A, B} &R`, as this cast would only succeed if the From 8fe43e99d181baa34747be09d3b3a02840d46042 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 4 Jan 2023 10:27:59 -0500 Subject: [PATCH 11/30] add section about potentially removing restricted types --- cadence/2022-12-14-auth-remodel.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index b36f3dad..ac1022eb 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -379,4 +379,14 @@ would be visible with such a reference. * One potential change this unlocks would be to restrict the creation of Capabilities based on the `auth`-ness of the reference they contain. We could restrict the `public` domain to be only for non-`auth` Capabilities, while the `private` domain would be only -for `auth`-references, which would prevent accidentally allowing anybody to get access to your `withdraw` function, for example. \ No newline at end of file +for `auth`-references, which would prevent accidentally allowing anybody to get access to your `withdraw` function, for example. + +* After this chnage, it is unclear whether restricted type would still have a place in the type system of Cadence. Consider that +now, with interface types specified both in the `auth` portion of the reference as well in the actual referenced type, there is an +element of redundancy in a type like `auth{T} &{T}` which is unnecessary. Users will be likely to instead specify reference with something like +`let ref = &r as auth{Provider} &Vault` instead of `let ref = &r as auth{Provider} &{Provider, Receiver}` given that downcasting is now possible. + + Restricted types would now only really be used as "interface sets", rather than restrictions, to specify that that a type should have a certain set + of functionality; e.g. writing a function that operates over all `Provider` types, for example. We can support this use case without restricted types, + however, the outside part `T` of the restricted type `T{Us}` is no longer necessary. One improvement we could make as part of this change would be to + remove "restriced types" and replace them with a "interface set" or "intersection" type that only contains the `{Us}`. \ No newline at end of file From 590fbe33d99acee0b0bc11e0779f317bd965e279 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 4 Jan 2023 22:36:58 -0500 Subject: [PATCH 12/30] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Naomi Liu <57917002+dreamsmasher@users.noreply.github.com> Co-authored-by: Bastian Müller --- cadence/2022-12-14-auth-remodel.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index ac1022eb..b6bca958 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -122,7 +122,7 @@ in the curly braces denote the interface types to which that reference has `auth to access `auth` members on the interfaces to which they have `auth` access. So, for example, given three interface definitions and a composite definition: -```cadecnce +```cadence pub resource interface A { access(auth) fun foo() } @@ -381,7 +381,7 @@ would be visible with such a reference. they contain. We could restrict the `public` domain to be only for non-`auth` Capabilities, while the `private` domain would be only for `auth`-references, which would prevent accidentally allowing anybody to get access to your `withdraw` function, for example. -* After this chnage, it is unclear whether restricted type would still have a place in the type system of Cadence. Consider that +* After this change, it is unclear whether restricted type would still have a place in the type system of Cadence. Consider that now, with interface types specified both in the `auth` portion of the reference as well in the actual referenced type, there is an element of redundancy in a type like `auth{T} &{T}` which is unnecessary. Users will be likely to instead specify reference with something like `let ref = &r as auth{Provider} &Vault` instead of `let ref = &r as auth{Provider} &{Provider, Receiver}` given that downcasting is now possible. @@ -389,4 +389,4 @@ element of redundancy in a type like `auth{T} &{T}` which is unnecessary. Users Restricted types would now only really be used as "interface sets", rather than restrictions, to specify that that a type should have a certain set of functionality; e.g. writing a function that operates over all `Provider` types, for example. We can support this use case without restricted types, however, the outside part `T` of the restricted type `T{Us}` is no longer necessary. One improvement we could make as part of this change would be to - remove "restriced types" and replace them with a "interface set" or "intersection" type that only contains the `{Us}`. \ No newline at end of file + remove "restricted types" and replace them with a "interface set" or "intersection" type that only contains the `{Us}`. \ No newline at end of file From f7f7079397cf7ead8dbc32f41e17b346a8abfc8e Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 10 Jan 2023 14:41:50 -0500 Subject: [PATCH 13/30] add section about rollout --- cadence/2022-12-14-auth-remodel.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index b6bca958..4fc9af31 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -237,6 +237,30 @@ by the authors in order to be used. This is because all code previously written a `&{Balance}` would prevent them from calling `pub` functions on `Provider` like `withdraw`), would become invalidated, allowing everyone to call `pub` members like `withdraw` unless those methods were updated to be `auth`. +### Rollout + +There are two cases that need handling to migrate to this new paradigm: contracts and data. + +Existing references in storage (i.e. `Capability` values) would need to be migrated, as they would no longer be semantically valid under the new system. +The simplest way to do this would be to ensure existing capabilities stay usable by converting existing capabilities and links' reference types to auth references, +e.g. `Capability<&Vault{Provider}>` -> `Capability`. Specifically, all existing references would become `auth` with regard to their entire borrow type, +as this does not add any additional power to these `Capability` values that did not previously exist. For example, `&Vault{Provider}` previously had the ability to call `withdraw`, +which was `pub` on `Provider`. After this change, it will still have the ability to call `withdraw`, as it is now `access(auth)` on `Provider`, but the reference now has `auth` +access to that type. + +However, this does not handle the previously mentioned problem wherein existing contracts become vulnerable to exploitation, as all their `pub` functions would become +accessible to anybody with any kind of reference to a contract or resource. + +One option to handle this case would be to "freeze" all existing contracts, preventing calling any code defined in them or interacting with their data until they are updated +at least once after the release of this feature (it's also possible that given the large number of breaking changes being released with Cadence 1.0, this restriction would happen +automatically and would not need special handling). Developers would be encouraged to give their contracts and resource the proper `auth` access modifiers. The concern here is that this would +require a large amount of coordination between developers; if Alice defined an interface that needed a function to be made `auth`, Bob's contract that implemented Alice's interface would +not be updatable to the new `auth` model until after Alice's contract was updated. + +Another option would be to implement a "versioning" system for references. In this solution, legacy references created prior to Cadence 1.0 would retain the old behavior; they would be +unable to be downcast, and would become `auth` for their borrow type as described above. New references created after the release of 1.0 would have the new behavior described in this FLIP. +However, we would likely want some way to "upgrade" a legacy reference to a new reference, and the method for doing so is not clear at this time. + ## Prior Art * This section needs filling out; would love to know if there are any languages out there doing something similar to this. From 1192f36af939acad455b8a892755a0a565f4f78f Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 20 Jan 2023 15:19:55 -0500 Subject: [PATCH 14/30] update rollout section to remove reference to versioned references --- cadence/2022-12-14-auth-remodel.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 4fc9af31..482dfdc4 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -251,15 +251,14 @@ access to that type. However, this does not handle the previously mentioned problem wherein existing contracts become vulnerable to exploitation, as all their `pub` functions would become accessible to anybody with any kind of reference to a contract or resource. -One option to handle this case would be to "freeze" all existing contracts, preventing calling any code defined in them or interacting with their data until they are updated +To handle this case we woukld "freeze" all existing contracts, preventing calling any code defined in them or interacting with their data until they are updated at least once after the release of this feature (it's also possible that given the large number of breaking changes being released with Cadence 1.0, this restriction would happen -automatically and would not need special handling). Developers would be encouraged to give their contracts and resource the proper `auth` access modifiers. The concern here is that this would -require a large amount of coordination between developers; if Alice defined an interface that needed a function to be made `auth`, Bob's contract that implemented Alice's interface would -not be updatable to the new `auth` model until after Alice's contract was updated. +automatically and would not need special handling). Developers would be encouraged to give their contracts and resource the proper `auth` access modifiers. -Another option would be to implement a "versioning" system for references. In this solution, legacy references created prior to Cadence 1.0 would retain the old behavior; they would be -unable to be downcast, and would become `auth` for their borrow type as described above. New references created after the release of 1.0 would have the new behavior described in this FLIP. -However, we would likely want some way to "upgrade" a legacy reference to a new reference, and the method for doing so is not clear at this time. +There are a few ways we could encourage developers to update their code and prevent security issues in the mean time. One would be to change subtyping such that `auth` methods +cannot subtype `pub` methods, so that when the NFT standards are updated to use `auth`, any concrete NFTs implementing them will need to change their implementations to use `auth` +in order to type check. An even more extreme option would be to remove support for the `pub` alias for `access(all)`, essentially breaking every single existing contract in +order to guarantee nothing is implicitly broken. ## Prior Art From bbff7fc61f570974c41453828743cce68e2907f3 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 2 Feb 2023 12:25:13 -0500 Subject: [PATCH 15/30] update FLIP to incorporate new entitlements idea, currently generated from interfaces --- cadence/2022-12-14-auth-remodel.md | 181 ++++++++++++++--------------- 1 file changed, 85 insertions(+), 96 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 482dfdc4..1f10ef8b 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -11,7 +11,7 @@ updated: 2022-12-16 ## Objective This FLIP proposes two major changes to the language: -1) The addition of a new access modifier on contracts, structs and resources: `auth` +1) The addition of new, bespoke access modifiers on fields and functions in composites and interfaces 2) A change to the behavior of reference types to add granular authority restrictions to specific interfaces, and allow safe downcasting ## Motivation @@ -28,7 +28,7 @@ that there would be no safety concerns in doing so; it should be possible to cal reference a user possesses. The proposed change is designed to overcome this limitation by allowing developers to directly mark which fields and functions -should be access-limited (using the new `auth` keyword), and which should be generally available to anyone. +should be access-limited using these new bespoke modifiers, and which should be generally available to anyone. ## User Benefit @@ -39,28 +39,31 @@ or because they are trying to write a generic method that works, say, on any `NF It would also increase safety, as we would be able to do additional checking on the values and types to which users create capabilities. For example, it is currently possible for a user to (presumably accidentally) create a public Capability directly to a `Vault` object, rather than a Capability to a `&{Balance, Receiver}` restricted type as is intended. With these changes, such a Capability still would -not be subject to having its funds `withdraw`n, as this `Vault` Capability would not be `auth`. In order to have access to an `auth` -method like `withdraw`, the user would need to explicitly create the Capability with an `auth` reference, and we could enforce statically +not be subject to having its funds `withdraw`n, as this `Vault` Capability would not be `auth(Provider)`. In order to have access to an `auth` +method like `withdraw`, the user would need to explicitly create the Capability with an `auth(Provider)` reference, and we could enforce statically (or warn) that `auth` reference capabilies should not be stored publicly. ## Design Proposal -### `auth` fields +### entitlement-access fields The first portion of this FLIP proposes to add a new access control modifier to field and function declarations in composite types: -`access(auth)`, which allows access to either the immediate owner of the resource (i.e. anybody who has the actual resource value), -or someone with an `auth` reference to the type on which the member is defined. +`access(X)`, which allows access to either the immediate owner of the resource (i.e. anybody who has the actual resource value), +or someone with an `auth(X)` reference to the type on which the member is defined. The `X` here can be the name of any interface (including +the interface currently being defined), in the inheritance chain of the composite or interface being defined. We call this `X` the "entitlement" +associated with the field or function, and a reference of `auth(X)` type is considered to possess an `X` entitlement. Like `access(contract)` and `access(account)`, this new modifier sits exactly between `pub` and `priv` (or equivalently `access(self)`) -in permissiveness; it allows less access than `pub`, but strictly more than `priv`, as an `auth` field or function can be used anywhere +in permissiveness; it allows less access than `pub`, but strictly more than `priv`, as an `access(X)` field or function can be used anywhere in the implementation of the composite. To see why, consider that `self` is necessarily of the same type as the composite, meaning -that the access rules defined above allow any `auth` members to be accessed on it. +that the access rules defined above allow any `access(I)` members to be accessed on it. As such, the following would be prohibited statically: ```cadence -pub resource R { - access(auth) fun foo() { ... } +pub resource interface I {} +pub resource R: I { + access(I) fun foo() { ... } } let r: &R = // ... r.foo() @@ -69,37 +72,36 @@ r.foo() while all of these would be permitted: ```cadence -pub resource R { - access(auth) fun foo() { ... } +pub resource interface I {} +pub resource R: I { + access(I) fun foo() { ... } pub fun bar() { self.foo() } } let r: @R = // ... r.foo() -let ref: auth &R = &r +let ref: auth(I) &R = &r ref.foo() ``` -Note also that the interface implementation rules allow a composite to implement an `access(auth)` interface member with a `pub` -composite member, as this is less restrictive, but not the other way around, as this would allow the interface type to gain more access -to the composite than should be possible. So this would be acceptable: +Note also that while the normal interface implementation subtyping rules would allow a composite to implement an `access(X)` interface member with a `pub` +composite member, as this is less restrictive, in order to prevent users from accidentally surrendering authority and security this way, we prevent this statically. +As such, the below code would not typecheck. ```cadence pub resource interface I { - access(auth) fun foo() + access(I) fun foo() } pub resource R: I { - pub fun foo() {} + pub fun foo() {} // must also be access(I) } ``` -since upcasting a value of type `&R` to type `&{I}` would not allow the reference to call any more functions. There is no similar concern -with downcasting a `&{I}` to a `&R` and gaining the ability to call `foo`, because `foo` is declared as `pub` on `R`, and we treat the -access control declarations on the concrete composite as the "source of truth" for the value's access control at runtime. +If users would like to expose an access-limited function to `pub` users, they can do so by wrapping the `access(X)` function in a `pub` function. -while this is not: +As with normal subtyping, this would also be statically rejected: ```cadence pub resource interface I { @@ -107,7 +109,7 @@ pub resource interface I { } pub resource R: I { - access(auth) fun foo() {} + access(I) fun foo() {} } ``` @@ -116,18 +118,17 @@ since if this were to typecheck, anybody with a `&R` reference could upcast it t ### Safely Downcastable References 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 -in the curly braces denote the interface types to which that reference has `auth` access. This permits these references -to access `auth` members on the interfaces to which they have `auth` access. So, for example, given three interface definitions and -a composite definition: +refer to the entire referenced value, but to the specific set of interfaces to which the referenced value has `auth` permissions (the reference's entitlements). +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 +in the parentheses denote the entitlements. This permits these references to access `access(T)` members for any `T` entitlement they possess. +So, for example, given three interface definitions and a composite definition: ```cadence pub resource interface A { - access(auth) fun foo() + access(A) fun foo() } pub resource interface B { - access(auth) fun bar() + access(B) fun bar() } pub resource interface C { pub fun baz() @@ -137,12 +138,12 @@ pub resource R: A, B, C { } ``` -a value of type `auth{A} &R` would be able to access `foo`, because the reference has `auth` access to `A`, but not `bar`, because -it does not have `auth` access to `B`. However, `baz` would be accessible on this reference, since it has `pub` access. +a value of type `auth(A) &R` would be able to access `foo`, because the reference has the `A` entitlement, but not `bar`, because +it does not have an entitlement for `B`. However, `baz` would be accessible on this reference, since it has `pub` access. -The interfaces over which a reference can be authorized must be a supertypes of the underlying referenced type: it is nonsensical, -for example, to express a type like `auth{A} &{B}`, since `A` and `B` are disjoint interfaces that do not share a heirarchy, and thus -the `auth` access to `A` granted by this reference would have no effect. As such, given a list of `I`s in a reference `auth{I1, I2} &T`, +A reference's entitlements must be a supertypes of the underlying referenced type: it is nonsensical, +for example, to express a type like `auth(A) &{B}`, since `A` and `B` are disjoint interfaces that do not share a heirarchy, and thus +the entitlement to `A` granted by this reference would have no effect. As such, given a list of `I`s in a reference `auth(I1, I2) &T`, we require that `I` appear in the conformance heirarchy of `T` if it is a composite type, or that `I` exists in the restriction set of `T` if it is a restricted type. @@ -151,41 +152,38 @@ non-`auth` references could not be downcast at all, since the sole purpose of th downcast. With the proposed change, all reference types can be downcast or upcast the same way any other type would be. So, for example `&{A} as! &R` would be valid, as would `&AnyResource as? &{B, C}` or `&{A} as? &{B}`, using the hierarchy defined above. -However, the `auth`-ness (and the set of interfaces for which the reference is `auth`) would not change on downcasting, nor would that set -be expandable via casting. The subtyping rules for `auth` references is that `auth {U1, U2, ... } &X <: auth {T1, T2, ... } &X` whenever `{U1, U2, ... }` -is a superset of `{T1, T2, ... }`, or equivalently `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, T = U`. Of course, all `auth` reference types +However, the `auth`-ness (and the reference's set of entitlements) would not change on downcasting, nor would that set +be expandable via casting. The subtyping rules for `auth` references is that `auth (U1, U2, ... ) &X <: auth (T1, T2, ... ) &X` whenever `{U1, U2, ...}` +is a superset of `{T1, T2, ...}`, or equivalently `∀T ∈ {T1, T2, ...}, ∃U ∈ {U1, U2, ...}, T = U`. Of course, all `auth` reference types would remain subtypes of all non-`auth` reference types as before. -As such, `auth{A, B} &R` would be statically upcastable to `auth{A} &R`, since this decreases the permissions on the +As such, `auth(A, B) &R` would be statically upcastable to `auth{A} &R`, since this decreases the permissions on the reference, it would require a runtime cast to go from `auth{A} &R` to `auth{A, B} &R`, as this cast would only succeed if the -runtime type of the reference was `auth` for both `A` and `B`. So in the code below: +runtime type of the reference was entitled to both `A` and `B`. So in the code below: ```cadence fun foo(ref: &R): Bool { - let authRef = ref as? auth{A, B} &R + let authRef = ref as? auth(A, B) &R return authRef != nil } let r <- create R() -let ref1 = &r as auth{A} &R -let ref2 = &r as auth{A, B, C} &R +let ref1 = &r as auth(A) &R +let ref2 = &r as auth(A, B, C) &R ``` -`foo` would return `true` when called with `ref2`, because the runtime type of `ref` in the failable cast is a subtype of `auth{A, B} &R`, since +`foo` would return `true` when called with `ref2`, because the runtime type of `ref` in the failable cast is a subtype of `auth(A, B) &R`, since `{A, B, C}` is a superset of `{A, B}`, but would return `false` when called with `ref1`, since `{A}` is not a superset of `{A, B}`. -Because only interface types can appear in the curly braces after the `auth` keyword, the `auth` keyword without a restriction set will denote -full (unrestricted) `auth` access to the reference type. E.g. `auth &R` would be a reference with full `auth` access to `R`. - ### Drawbacks This dramatically increases the complexity of references and capabilities by requiring users to think not only about which types they -are creating the references with, but also which `auth` accesses they are providing on each reference they create. It also increases +are creating the references with, but also which entitlements they are providing on each reference they create. It also increases the implementation complexity of the type system significantly by adding an additional dimension to the reference type's subtype heirarchy. ### Best Practices This would encourage users to use the new `auth` access modifier to restrict access on their contracts and resources; and use different -`auth` restriction sets to control access. +entitlement sets to control access. ### Tutorials and Examples @@ -193,7 +191,7 @@ With this new design, the `Vault` heirarchy might be written like so: ```cadence pub resource interface Provider { - access(auth) fun withdraw(amount: UFix64): @Vault { + access(Provider) fun withdraw(amount: UFix64): @Vault { // ... } } @@ -209,7 +207,7 @@ pub resource interface Balance { } pub resource Vault: Provider, Receiver, Balance { - access(auth) fun withdraw(amount: UFix64): @Vault { + access(Provider) fun withdraw(amount: UFix64): @Vault { // ... } pub fun deposit(from: @Vault) { @@ -220,14 +218,14 @@ pub resource Vault: Provider, Receiver, Balance { ``` 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` reference, 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`. +They would also be able to cast this to a `&Vault` reference, but because this reference is not entitled to `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) &Vault` and call `withdraw`. ### Compatibility This would not be backwards compatible with existing code, and thus would need to be part of the Stable Cadence release. -Most contracts would need to be audited and rewritten to use the new `auth` access modifier, as they would immediately become +Most contracts would need to be audited and rewritten to use the new access modifiers, as they would immediately become vulnerable to having `pub` fields accessed from now-downcastable references. ### User Impact @@ -235,17 +233,17 @@ vulnerable to having `pub` fields accessed from now-downcastable references. There is a huge user impact to rolling out this change; every single contract would become invalid, and would need to be manually audited by the authors in order to be used. This is because all code previously written using the old model of access control (wherein giving someone a `&{Balance}` would prevent them from calling `pub` functions on `Provider` like `withdraw`), would become invalidated, allowing everyone -to call `pub` members like `withdraw` unless those methods were updated to be `auth`. +to call `pub` members like `withdraw` unless those methods were updated to be `access(Provider)`. ### Rollout There are two cases that need handling to migrate to this new paradigm: contracts and data. Existing references in storage (i.e. `Capability` values) would need to be migrated, as they would no longer be semantically valid under the new system. -The simplest way to do this would be to ensure existing capabilities stay usable by converting existing capabilities and links' reference types to auth references, -e.g. `Capability<&Vault{Provider}>` -> `Capability`. Specifically, all existing references would become `auth` with regard to their entire borrow type, +The simplest way to do this would be to ensure existing capabilities stay usable by converting existing capabilities and links' reference types to `auth` references, +e.g. `Capability<&Vault{Provider}>` -> `Capability`. Specifically, all existing references would become `auth` with regard to their entire borrow type, as this does not add any additional power to these `Capability` values that did not previously exist. For example, `&Vault{Provider}` previously had the ability to call `withdraw`, -which was `pub` on `Provider`. After this change, it will still have the ability to call `withdraw`, as it is now `access(auth)` on `Provider`, but the reference now has `auth` +which was `pub` on `Provider`. After this change, it will still have the ability to call `withdraw`, as it is now `access(Provider)` on `Provider`, but the reference now has `auth(Provider)` access to that type. However, this does not handle the previously mentioned problem wherein existing contracts become vulnerable to exploitation, as all their `pub` functions would become @@ -253,16 +251,7 @@ accessible to anybody with any kind of reference to a contract or resource. To handle this case we woukld "freeze" all existing contracts, preventing calling any code defined in them or interacting with their data until they are updated at least once after the release of this feature (it's also possible that given the large number of breaking changes being released with Cadence 1.0, this restriction would happen -automatically and would not need special handling). Developers would be encouraged to give their contracts and resource the proper `auth` access modifiers. - -There are a few ways we could encourage developers to update their code and prevent security issues in the mean time. One would be to change subtyping such that `auth` methods -cannot subtype `pub` methods, so that when the NFT standards are updated to use `auth`, any concrete NFTs implementing them will need to change their implementations to use `auth` -in order to type check. An even more extreme option would be to remove support for the `pub` alias for `access(all)`, essentially breaking every single existing contract in -order to guarantee nothing is implicitly broken. - -## Prior Art - -* This section needs filling out; would love to know if there are any languages out there doing something similar to this. +automatically and would not need special handling). Developers would be encouraged to give their contracts and resources the proper access modifiers. ## Alternatives Considered @@ -299,7 +288,7 @@ to no say in the access-control on their value beyond selecting which interfaces * The upcoming proposed changes to permit interfaces to conform to other interfaces will necessarily change the subtyping rules for `auth` references, as it would no longer be sufficient to compare the sets for the two `auth` references just based on membership. The membership check would still exist, but it would function as a width subtyping rule, in addition to a depth rule based on -interface chains. I.e. it would be the case that for two auth references, `auth{U} &X <: auth{T1} &X`, if `U <: T`. +interface chains. I.e. it would be the case that for two auth references, `auth(U) &X <: auth(T1) &X`, if `U <: T`. We can combine this depth rule with the already existing width rule to yield a combined subtyping rule: `auth{U1, U2, ... } &X <: auth{T1, T2, ... } &X`, whenever `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, U <: T`. @@ -315,8 +304,8 @@ whenever `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, U <: T`. pub resource R: E {} ``` - we would have `auth{E} &R <: auth{C} &R <: auth{A} &R <: &R` and `auth{E} &R <: auth{D} &R <: auth{B} &R <: &R`. - It would also be the case that `auth{D, C} &R <: auth{A} &R` as well, because `C <: A`, and that `auth{E} &R <: auth{B, C} &R`, + we would have `auth(E) &R <: auth(C) &R <: auth(A) &R <: &R` and `auth(E) &R <: auth(D) &R <: auth(B) &R <: &R`. + It would also be the case that `auth(D, C) &R <: auth(A) &R` as well, because `C <: A`, and that `auth(E) &R <: auth(B, C) &R`, because `E <: B` and `E <: C`. * It is unclear how this should interact with the new attachments feature. Prior to this proposal a functional mental model @@ -325,28 +314,28 @@ The existing behavior preventing downcasting of non-`auth` references would prev would have access to functions like `withdraw` via the `base` reference) if they only possessed a `&{Balance}`, since only `Balance`-attachments would be visible with such a reference. - One simple approach would be to allow attachments to be accessed only on values that have `auth` access to the attachment's `base` type. - I.e. given an `attachment A for I`, `v[A]` would be permissible when `v` has type `auth{I} &R` but not when `v` is just a regular `&R`, - or even an `&{I}`. Functionally this would mean that attachments would become `auth` fields in the mental model described above. + One simple approach would be to allow attachments to be accessed only on values that have an entitlement to the attachment's `base` type. + I.e. given an `attachment A for I`, `v[A]` would be permissible when `v` has type `auth(I) &R` but not when `v` is just a regular `&R`, + or even an `&{I}`. Functionally this would mean that attachments would become `access()` fields in the mental model described above. - This approach is simple but very restrictive. In the motivating `KittyHat` use case for example, only users with an `auth`-reference + This approach is simple but very restrictive. In the motivating `KittyHat` use case for example, only users with an `auth`-reference giving them an entitlement to the `Kitty` resource would be able to use the `Hat`. This would either make the `KittyHat` almost unusable without giving out an unreasonable amount of authority to users, or require the author of the `Kitty` to write a specific interface describing the set of features they would want an attachment to be able to use. This latter case would completely defeat the point of attachments in the first place, since they are designed to be usable with no prior planning from the base value's author. - An alternative would be to implicitly parameterize the attachment's `auth` access over the `auth` access of its base value. In this model, - attachments would remain `pub` accessible, but would themselves permit the declaration of `auth` members. The `base` value, + An alternative would be to implicitly parameterize the attachment's entitlements over the entitlements of its base value. In this model, + attachments would remain `pub` accessible, but would themselves permit the declaration of `access`-limited members. The `base` value, which currently is simply a `&R` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have its `auth`-ness - depend on the `auth`-ness of the member function in question. In a member declaration `access(auth) fun foo()` in `A`, the `base` variable would have - type `auth &R`, while in a member declaration in `A` `pub fun bar()`, `base` would just be an `&R`. This would effectively mean that the `auth`-access - members of the `base` would only be available to the attachment author in `auth`-access members on the attachment. Similarly, in an `auth`-access - member, the `self` reference of the attachment `A` would be `auth &A`, while in a `pub`-access member it would just be `&A`. + depend on the `auth`-ness of the member function in question. In a member declaration `access(X) fun foo()` in `A`, the `base` variable would have + type `auth(X) &R`, while in a member declaration in `A` `pub fun bar()`, `base` would just be an `&R`. This would effectively mean that the `access(X)` + members of the `base` would only be available to the attachment author in `auth(X)` members on the attachment. Similarly, in an `access(X)` + member, the `self` reference of the attachment `A` would be `auth(X) &A`, while in a `pub`-access member it would just be `&A`. This would then be combined with a change to the attachment access rules: rather than `v[A]` always returning an `&A?` value, the type of the returned - attachment reference would depend on the type of `v`. If `v` is not a reference, or a reference with `auth` access to `A`'s `base` type, then `v[A]` would return - an `(auth &A)?` type, while if the reference did not have access to `A`'s `base`, then the access would be a regular `&A?` type. This would prevent - the attachment from accessing `auth` members on its `base` unless the specific instance of that base to which it is attached has the proper `auth` access. + attachment reference would depend on the type of `v`. If `v` is not a reference, or a reference with an entitlement to `A`'s `base` type, then `v[A]` would return + an `(auth(base) &A)?` type, while if the reference did not have access to `A`'s `base`, then the access would be a regular `&A?` type. This would prevent + the attachment from accessing `auth` members on its `base` unless the specific instance of that base to which it is attached has the proper entitlement. So, for example, given the following declaration: ```cadence @@ -360,10 +349,10 @@ would be visible with such a reference. return <-vault } - access(auth) fun withdraw(_ amount: UFix64): @Vault { + access(Provider) fun withdraw(_ amount: UFix64): @Vault { let convertedAmount = self.convert(amount) - // this is permitted because this function has `auth` access - // on the attachment, and thus `base` has type `auth{Provider} &{Provider}` + // this is permitted because this function has an entitlement to `Provider` + // on the attachment, and thus `base` has type `auth(Provider) &{Provider}` return <-base.withdraw(amount: amount) } @@ -393,21 +382,21 @@ would be visible with such a reference. } let vault <- attach CurrencyConverter() to <-create Vault(balance: /*...*/) - let authVaultReference = &vault as auth{Provider} &Vault - let converterRef = authVaultReference[CurrencyConverter]! // has type auth &CurrencyConverter, can call `withdraw` + let authVaultReference = &vault as auth(Provider) &Vault + let converterRef = authVaultReference[CurrencyConverter]! // has type auth(Provider) &CurrencyConverter, can call `withdraw` - let otherVaultReference = &vault as auth{Balance} &Vault + let otherVaultReference = &vault as auth(Balance) &Vault let otheConverterRef = otherVaultReference[CurrencyConverter]! // has type &CurrencyConverter, cannot call `withdraw` ``` -* One potential change this unlocks would be to restrict the creation of Capabilities based on the `auth`-ness of the reference -they contain. We could restrict the `public` domain to be only for non-`auth` Capabilities, while the `private` domain would be only +* One potential change this unlocks would be to restrict the creation of Capabilities based on the entitlements +they contain. We could restrict the `public` domain to be only for non-entitled Capabilities, while the `private` domain would be only for `auth`-references, which would prevent accidentally allowing anybody to get access to your `withdraw` function, for example. * After this change, it is unclear whether restricted type would still have a place in the type system of Cadence. Consider that now, with interface types specified both in the `auth` portion of the reference as well in the actual referenced type, there is an -element of redundancy in a type like `auth{T} &{T}` which is unnecessary. Users will be likely to instead specify reference with something like -`let ref = &r as auth{Provider} &Vault` instead of `let ref = &r as auth{Provider} &{Provider, Receiver}` given that downcasting is now possible. +element of redundancy in a type like `auth(T) &{T}` which is unnecessary. Users will be likely to instead specify reference with something like +`let ref = &r as auth(Provider) &Vault` instead of `let ref = &r as auth(Provider) &{Provider, Receiver}` given that downcasting is now possible. Restricted types would now only really be used as "interface sets", rather than restrictions, to specify that that a type should have a certain set of functionality; e.g. writing a function that operates over all `Provider` types, for example. We can support this use case without restricted types, From f842b765c33fc95061e7a02566cef270fe68a2d4 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 2 Feb 2023 12:32:13 -0500 Subject: [PATCH 16/30] add section about standalone entitlements --- cadence/2022-12-14-auth-remodel.md | 184 ++++++++++++++++------------- 1 file changed, 102 insertions(+), 82 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 1f10ef8b..f374fa87 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -174,6 +174,73 @@ let ref2 = &r as auth(A, B, C) &R `foo` would return `true` when called with `ref2`, because the runtime type of `ref` in the failable cast is a subtype of `auth(A, B) &R`, since `{A, B, C}` is a superset of `{A, B}`, but would return `false` when called with `ref1`, since `{A}` is not a superset of `{A, B}`. +#### Attachments and Entitlements + +Attachments would interact with entitlements and access-limited members in a nuanced but intuitive manner, where the attachment's entitlements +are implicitly parameterized over the entitlements of its `base` value. Attachments would remain `pub` accessible, but would permit the declaration of `access`-limited members. +The `base` value, which currently is simply a `&R` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have its entitlements +depend on the entitlements of the member function in question. In a member declaration `access(X) fun foo()` in `A`, the `base` variable would have +type `auth(X) &R`, while in a member declaration in `A` `pub fun bar()`, `base` would just be an `&R`. This would effectively mean that the `access(X)` +members of the `base` would only be available to the attachment author in `auth(X)` members on the attachment. Similarly, in an `access(X)` +member, the `self` reference of the attachment `A` would be `auth(X) &A`, while in a `pub`-access member it would just be `&A`. + +This would then be combined with a change to the attachment access rules: rather than `v[A]` always returning an `&A?` value, the type of the returned +attachment reference would depend on the type of `v`. If `v` is not a reference, or a reference with an entitlement to `A`'s `base` type, then `v[A]` would return +an `(auth(base) &A)?` type, while if the reference did not have access to `A`'s `base`, then the access would be a regular `&A?` type. This would prevent +the attachment from accessing `auth` members on its `base` unless the specific instance of that base to which it is attached has the proper entitlement. + +So, for example, given the following declaration: +```cadence +attachment CurrencyConverter for Provider { + pub fun convert(_ amount: UFix64): UFix64 { + // ... + } + + pub fun convertVault(_ vault: @Vault): @Vault { + vault.balance = self.convert(vault.balance) + return <-vault + } + + access(Provider) fun withdraw(_ amount: UFix64): @Vault { + let convertedAmount = self.convert(amount) + // this is permitted because this function has an entitlement to `Provider` + // on the attachment, and thus `base` has type `auth(Provider) &{Provider}` + return <-base.withdraw(amount: amount) + } + + pub fun deposit (from: @Vault) { + // cast is permissable under the new reference casting rules + let baseReceiverOptional = base as? &{Receiver} + if let baseReceiver = baseReceiverOptional { + let convertedVault <- self.convertVault(<-from) + // this is ok because `deposit` has `pub` access + baseReceiver.deposit(from: <-convertedVault) + } + } + + pub fun maliciousStealingFunctionA(_ amount: UFix64): @Vault { + // This fails statically, as `self` here is just an `&A` + // because `maliciousStealingFunctionA`'s access is `pub`, + // and therefore `self` does not have access to `withdraw` + return <-self.withdraw(amount) + } + + pub fun maliciousStealingFunctionB(_ amount: UFix64): @Vault { + // This fails statically, as `base` here is just an `&{Provider}` + // because `maliciousStealingFunctionB`'s access is `pub`, + // and therefore `base` does not have access to `withdraw` + return <-base.withdraw(amount: amount) + } +} + +let vault <- attach CurrencyConverter() to <-create Vault(balance: /*...*/) +let authVaultReference = &vault as auth(Provider) &Vault +let converterRef = authVaultReference[CurrencyConverter]! // has type auth(Provider) &CurrencyConverter, can call `withdraw` + +let otherVaultReference = &vault as auth(Balance) &Vault +let otheConverterRef = otherVaultReference[CurrencyConverter]! // has type &CurrencyConverter, cannot call `withdraw` +``` + ### Drawbacks This dramatically increases the complexity of references and capabilities by requiring users to think not only about which types they @@ -308,87 +375,6 @@ whenever `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, U <: T`. It would also be the case that `auth(D, C) &R <: auth(A) &R` as well, because `C <: A`, and that `auth(E) &R <: auth(B, C) &R`, because `E <: B` and `E <: C`. -* It is unclear how this should interact with the new attachments feature. Prior to this proposal a functional mental model -for attachments was to treat them as implicit type-disambiguated `pub` fields on the resource or struct to which they were attached. -The existing behavior preventing downcasting of non-`auth` references would prevent someone from accessing a `Vault`-attachment (which -would have access to functions like `withdraw` via the `base` reference) if they only possessed a `&{Balance}`, since only `Balance`-attachments -would be visible with such a reference. - - One simple approach would be to allow attachments to be accessed only on values that have an entitlement to the attachment's `base` type. - I.e. given an `attachment A for I`, `v[A]` would be permissible when `v` has type `auth(I) &R` but not when `v` is just a regular `&R`, - or even an `&{I}`. Functionally this would mean that attachments would become `access()` fields in the mental model described above. - - This approach is simple but very restrictive. In the motivating `KittyHat` use case for example, only users with an `auth`-reference giving them an entitlement - to the `Kitty` resource would be able to use the `Hat`. This would either make the `KittyHat` almost unusable without giving out - an unreasonable amount of authority to users, or require the author of the `Kitty` to write a specific interface describing the set of - features they would want an attachment to be able to use. This latter case would completely defeat the point of attachments in the first place, - since they are designed to be usable with no prior planning from the base value's author. - - An alternative would be to implicitly parameterize the attachment's entitlements over the entitlements of its base value. In this model, - attachments would remain `pub` accessible, but would themselves permit the declaration of `access`-limited members. The `base` value, - which currently is simply a `&R` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have its `auth`-ness - depend on the `auth`-ness of the member function in question. In a member declaration `access(X) fun foo()` in `A`, the `base` variable would have - type `auth(X) &R`, while in a member declaration in `A` `pub fun bar()`, `base` would just be an `&R`. This would effectively mean that the `access(X)` - members of the `base` would only be available to the attachment author in `auth(X)` members on the attachment. Similarly, in an `access(X)` - member, the `self` reference of the attachment `A` would be `auth(X) &A`, while in a `pub`-access member it would just be `&A`. - - This would then be combined with a change to the attachment access rules: rather than `v[A]` always returning an `&A?` value, the type of the returned - attachment reference would depend on the type of `v`. If `v` is not a reference, or a reference with an entitlement to `A`'s `base` type, then `v[A]` would return - an `(auth(base) &A)?` type, while if the reference did not have access to `A`'s `base`, then the access would be a regular `&A?` type. This would prevent - the attachment from accessing `auth` members on its `base` unless the specific instance of that base to which it is attached has the proper entitlement. - - So, for example, given the following declaration: - ```cadence - attachment CurrencyConverter for Provider { - pub fun convert(_ amount: UFix64): UFix64 { - // ... - } - - pub fun convertVault(_ vault: @Vault): @Vault { - vault.balance = self.convert(vault.balance) - return <-vault - } - - access(Provider) fun withdraw(_ amount: UFix64): @Vault { - let convertedAmount = self.convert(amount) - // this is permitted because this function has an entitlement to `Provider` - // on the attachment, and thus `base` has type `auth(Provider) &{Provider}` - return <-base.withdraw(amount: amount) - } - - pub fun deposit (from: @Vault) { - // cast is permissable under the new reference casting rules - let baseReceiverOptional = base as? &{Receiver} - if let baseReceiver = baseReceiverOptional { - let convertedVault <- self.convertVault(<-from) - // this is ok because `deposit` has `pub` access - baseReceiver.deposit(from: <-convertedVault) - } - } - - pub fun maliciousStealingFunctionA(_ amount: UFix64): @Vault { - // This fails statically, as `self` here is just an `&A` - // because `maliciousStealingFunctionA`'s access is `pub`, - // and therefore `self` does not have access to `withdraw` - return <-self.withdraw(amount) - } - - pub fun maliciousStealingFunctionB(_ amount: UFix64): @Vault { - // This fails statically, as `base` here is just an `&{Provider}` - // because `maliciousStealingFunctionB`'s access is `pub`, - // and therefore `base` does not have access to `withdraw` - return <-base.withdraw(amount: amount) - } - } - - let vault <- attach CurrencyConverter() to <-create Vault(balance: /*...*/) - let authVaultReference = &vault as auth(Provider) &Vault - let converterRef = authVaultReference[CurrencyConverter]! // has type auth(Provider) &CurrencyConverter, can call `withdraw` - - let otherVaultReference = &vault as auth(Balance) &Vault - let otheConverterRef = otherVaultReference[CurrencyConverter]! // has type &CurrencyConverter, cannot call `withdraw` - ``` - * One potential change this unlocks would be to restrict the creation of Capabilities based on the entitlements they contain. We could restrict the `public` domain to be only for non-entitled Capabilities, while the `private` domain would be only for `auth`-references, which would prevent accidentally allowing anybody to get access to your `withdraw` function, for example. @@ -401,4 +387,38 @@ element of redundancy in a type like `auth(T) &{T}` which is unnecessary. Users Restricted types would now only really be used as "interface sets", rather than restrictions, to specify that that a type should have a certain set of functionality; e.g. writing a function that operates over all `Provider` types, for example. We can support this use case without restricted types, however, the outside part `T` of the restricted type `T{Us}` is no longer necessary. One improvement we could make as part of this change would be to - remove "restricted types" and replace them with a "interface set" or "intersection" type that only contains the `{Us}`. \ No newline at end of file + remove "restricted types" and replace them with a "interface set" or "intersection" type that only contains the `{Us}`. + +* One large open question about this is whether entitlements should be a separate language object from interfaces. The current proposal allows users to use +interfaces as entitlements, since currently in Cadence one of the functions of interfaces is to limit access the way that entitlements will do. However, +there isn't any strong theoretical link between the polymorphism behavior of interfaces and this access control functionality, so it might be possible to +separate these concepts and allow declaring entitlements separately. An example of such functionality might look like this: + + ```cadence + contract FungibleToken { + resource interface Provider { + entitlement withdraw + + access(withdraw) fun withdraw(amount: UFix64): @Vault {} + } + + resource interface Receiver { + access(all) fun deposit(from: @Vault) {} + } + + resource interface Balance { + access(all) fun balance(): UFix64 {} + } + + resource interface Vault: Provider, Receiver, Balance {} + } + + resource FlowVault: FungibleToken.Vault { + access(FungibleToken.Provider.withdraw) fun withdraw(amount: UFix64): @Vault {} + access(all) fun deposit(from: @Vault) {} + access(all) fun balance(): UFix64 {} + + access(class) fun internalUtilityFunction(otherVault: auth(FungibleToken.Provider.withdraw)& FlowVault) {} + access(FungibleToken.Provider.withdraw) fun withdrawAll(): @Vault {} + } + ``` \ No newline at end of file From 30ed8e20b3992c861f1cb466be350edcb1c44d3d Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 17 Feb 2023 14:39:27 -0500 Subject: [PATCH 17/30] update FLIP to refer to separate entitlement language objects --- cadence/2022-12-14-auth-remodel.md | 289 +++++++++++++++++------------ 1 file changed, 173 insertions(+), 116 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index f374fa87..d1c81b28 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -3,15 +3,15 @@ status: draft flip: NNN (do not set) authors: Daniel Sainati (daniel.sainati@dapperlabs.com) sponsor: Daniel Sainati (daniel.sainati@dapperlabs.com) -updated: 2022-12-16 +updated: 2023-02-17 --- -# `auth` model changes +# Entitlements ## Objective This FLIP proposes two major changes to the language: -1) The addition of new, bespoke access modifiers on fields and functions in composites and interfaces +1) The addition of new, bespoke access modifiers on fields and functions in composites and interfaces in the form of entitlements 2) A change to the behavior of reference types to add granular authority restrictions to specific interfaces, and allow safe downcasting ## Motivation @@ -45,13 +45,90 @@ method like `withdraw`, the user would need to explicitly create the Capability ## Design Proposal -### entitlement-access fields +### Entitlements -The first portion of this FLIP proposes to add a new access control modifier to field and function declarations in composite types: +The first part of this FLIP proposes to add a new declaration type to Cadence: `entitlement`s. `entitlement`s are declared similarly to interfaces, but +with a few key differences. They use the following syntax: `pub? entitlement { ... }`, where the body of the `entitlement` contains a list of +functions or fields like in an interfaces. However, it is important to note three key distinctions: + +1) `entitlement`s are not kinded the way that interfaces are; they are neither resources nor structs, and as such cannot appear in any type position where a +kinded type is expected. + +2) `entitlement` functions cannot contain default implementations, pre-conditions, or post-conditions. `entitlement`s are used purely for access control, and +contain no polymorphism functionality like interfaces do. + +3) `entitlement` members are not declared with an access modifier the way that interface members are; this is because the `entitlement` is used to define +the access of these members on composites and interfaces, and thus it would be nonsensical for the entitlement definition itself to contain an access modifier. + +As such, the following is a valid entitlement definition: + +```cadence +pub entitlement E { + fun foo(a: Int) + let x: String +} +``` + +while the following are not: + +```cadence +pub entitlement A { + pub fun foo(a: Int) // cannot have an access modifier +} +pub resource entitlement A { // entitlement is not kinded + fun foo(a: Int) +} +pub entitlement A { + fun foo(a: Int) {} // cannot have a body +} +``` + +### Entitlement-access fields + +To go with these `entitlement` declarations, this FLIP proposes to add a new access control modifier to field and function declarations in composite types: `access(X)`, which allows access to either the immediate owner of the resource (i.e. anybody who has the actual resource value), -or someone with an `auth(X)` reference to the type on which the member is defined. The `X` here can be the name of any interface (including -the interface currently being defined), in the inheritance chain of the composite or interface being defined. We call this `X` the "entitlement" -associated with the field or function, and a reference of `auth(X)` type is considered to possess an `X` entitlement. +or someone with an `auth(X)` reference to the type on which the member is defined. The `X` here can be the qualified name of any entitlement, but +the function or field definition that uses that access modifier must match its definition in the `X` entitlement. So, this would be allowed: + +```cadence +entitlement E { + fun foo(a: Int) +} +resource R { + access(E) fun foo(a: Int) { } +} +``` + +while this would not: + +```cadence +entitlement E { + fun foo(a: Int) +} +resource R { + access(E) fun foo(a: String) { } // definition does not match `foo`'s definition in `E`. +} +``` + +A single member definition can include multiple entitlements; in which case the member is accessible to any `auth` reference with any +of those entitlements. As such, that member's definition must match all of its definitions in each of the `entitlement` declarations being +used. + +Additionally, because we only check that each member using an `entitlement` matches that entitlement's declaration of the member, it is not +necessary for a composite or interface to use every single member on an `entitlement` if they do not want or need to. So, for example, this code is valid: + +```cadence +entitlement E { + fun foo(a: A) {} + fun bar(b: B) {} +} +resource R { + access(E) foo(a: A) {} +} +``` + +because `foo`'s definition matches its definition in `E`. There is no check that `R` implmements all the members in `E`, however. This does not +pose an issue for type safety because `entitlement`s are never used for polymorphism, only access control. Like `access(contract)` and `access(account)`, this new modifier sits exactly between `pub` and `priv` (or equivalently `access(self)`) in permissiveness; it allows less access than `pub`, but strictly more than `priv`, as an `access(X)` field or function can be used anywhere @@ -61,9 +138,9 @@ that the access rules defined above allow any `access(I)` members to be accessed As such, the following would be prohibited statically: ```cadence -pub resource interface I {} -pub resource R: I { - access(I) fun foo() { ... } +pub entitlement E {} +pub resource R { + access(E) fun foo() { ... } } let r: &R = // ... r.foo() @@ -72,16 +149,16 @@ r.foo() while all of these would be permitted: ```cadence -pub resource interface I {} -pub resource R: I { - access(I) fun foo() { ... } +pub entitlement E {} +pub resource R { + access(E) fun foo() { ... } pub fun bar() { self.foo() } } let r: @R = // ... r.foo() -let ref: auth(I) &R = &r +let ref: auth(E) &R = &r ref.foo() ``` @@ -90,12 +167,16 @@ composite member, as this is less restrictive, in order to prevent users from ac As such, the below code would not typecheck. ```cadence +pub entitlement E { + fun foo() +} + pub resource interface I { - access(I) fun foo() + access(E) fun foo() } pub resource R: I { - pub fun foo() {} // must also be access(I) + pub fun foo() {} // must also be access(E) } ``` @@ -104,12 +185,16 @@ If users would like to expose an access-limited function to `pub` users, they ca As with normal subtyping, this would also be statically rejected: ```cadence +pub entitlement E { + fun foo() +} + pub resource interface I { pub fun foo() } pub resource R: I { - access(I) fun foo() {} + access(E) fun foo() {} } ``` @@ -119,33 +204,51 @@ since if this were to typecheck, anybody with a `&R` reference could upcast it t 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 (the reference's entitlements). -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 -in the parentheses denote the entitlements. This permits these references to access `access(T)` members for any `T` entitlement they possess. -So, for example, given three interface definitions and a composite definition: +To express this, the `auth` keyword can now be used with similar syntax to that of restricted types: `auth(E1, E2, ...)`, where the `E`s +in the parentheses denote the entitlements. This permits these references to access `access(E)` members for any `E` entitlement they possess. +So, for example, given two entitlement definitions and a composite definition: ```cadence -pub resource interface A { - access(A) fun foo() -} -pub resource interface B { - access(B) fun bar() +pub entitlement A { + fun foo() } -pub resource interface C { - pub fun baz() +pub entitlement B { + fun bar() } -pub resource R: A, B, C { - // ... +pub resource R { + access(A) fun foo() { ... } + access(B) fun bar() { ... } + pub fun baz() { ... } } ``` a value of type `auth(A) &R` would be able to access `foo`, because the reference has the `A` entitlement, but not `bar`, because it does not have an entitlement for `B`. However, `baz` would be accessible on this reference, since it has `pub` access. -A reference's entitlements must be a supertypes of the underlying referenced type: it is nonsensical, -for example, to express a type like `auth(A) &{B}`, since `A` and `B` are disjoint interfaces that do not share a heirarchy, and thus -the entitlement to `A` granted by this reference would have no effect. As such, given a list of `I`s in a reference `auth(I1, I2) &T`, -we require that `I` appear in the conformance heirarchy of `T` if it is a composite type, or that `I` exists in the restriction set of `T` -if it is a restricted type. +A reference's entitlements on creation must be valid entitlements of the underlying referenced type: it is nonsensical, given a set of definitions like: + +```cadence +pub entitlement A { + fun foo() +} +pub entitlement B { + fun bar() +} +pub resource R { + access(A) fun foo() { ... } +} +``` + +to create a reference like like `auth(B) &R`, since `R` has no functions with `B` entitlements. Thus + +```cadence +let r <- create R() +let ref = &r as auth(B) &R // cannot take a reference to `r` with a `B` entitlement +``` + +would fail statically. However, because the type on the right-hand side of the `&` may be an interface, just given an arbitrary static +reference type like `auth(B) &{I}`, we cannot know statically that some `R` does not exist such that `R <: I` and `R` has a function +with a `B` entitlement. As such we only enforce that the entitlements are valid when references are created, not when reference types are expressed. The other part of this change is to remove the limitations on resource downcasting that used to exist. Prior to this change, non-`auth` references could not be downcast at all, since the sole purpose of the `auth` keyword was to indicate that references could be @@ -185,12 +288,19 @@ members of the `base` would only be available to the attachment author in `auth( member, the `self` reference of the attachment `A` would be `auth(X) &A`, while in a `pub`-access member it would just be `&A`. This would then be combined with a change to the attachment access rules: rather than `v[A]` always returning an `&A?` value, the type of the returned -attachment reference would depend on the type of `v`. If `v` is not a reference, or a reference with an entitlement to `A`'s `base` type, then `v[A]` would return -an `(auth(base) &A)?` type, while if the reference did not have access to `A`'s `base`, then the access would be a regular `&A?` type. This would prevent +attachment reference would depend on the type of `v`. If `v` is not a reference, then any access `v[A]` would be fully authorized with type `(auth(owner) &A)?` (or similar +super-entitlement keyword). If `v` is a reference, then the access `v[A]` would return a reference to `A` with the same set of entitlements as `v`. This would prevent the attachment from accessing `auth` members on its `base` unless the specific instance of that base to which it is attached has the proper entitlement. So, for example, given the following declaration: ```cadence +entitlement Withdraw { + fun withdraw(_ amount: UFix64): @Vault +} +interface Provider { + access(Withdraw) fun withdraw(_ amount: UFix64): @Vault +} + attachment CurrencyConverter for Provider { pub fun convert(_ amount: UFix64): UFix64 { // ... @@ -201,10 +311,10 @@ attachment CurrencyConverter for Provider { return <-vault } - access(Provider) fun withdraw(_ amount: UFix64): @Vault { + access(Withdraw) fun withdraw(_ amount: UFix64): @Vault { let convertedAmount = self.convert(amount) - // this is permitted because this function has an entitlement to `Provider` - // on the attachment, and thus `base` has type `auth(Provider) &{Provider}` + // this is permitted because this function has an entitlement to `Withdraw` + // on the attachment, and thus `base` has type `auth(Withdraw) &{Provider}` return <-base.withdraw(amount: amount) } @@ -234,10 +344,10 @@ attachment CurrencyConverter for Provider { } let vault <- attach CurrencyConverter() to <-create Vault(balance: /*...*/) -let authVaultReference = &vault as auth(Provider) &Vault -let converterRef = authVaultReference[CurrencyConverter]! // has type auth(Provider) &CurrencyConverter, can call `withdraw` +let authVaultReference = &vault as auth(Withdraw) &Vault +let converterRef = authVaultReference[CurrencyConverter]! // has type auth(Withdraw) &CurrencyConverter, can call `withdraw` -let otherVaultReference = &vault as auth(Balance) &Vault +let otherVaultReference = &vault as &Vault let otheConverterRef = otherVaultReference[CurrencyConverter]! // has type &CurrencyConverter, cannot call `withdraw` ``` @@ -257,8 +367,12 @@ entitlement sets to control access. With this new design, the `Vault` heirarchy might be written like so: ```cadence +pub entitlement Withdraw { + fun withdraw(amount: UFix64): @Vault +} + pub resource interface Provider { - access(Provider) fun withdraw(amount: UFix64): @Vault { + access(Withdraw) fun withdraw(amount: UFix64): @Vault { // ... } } @@ -274,7 +388,7 @@ pub resource interface Balance { } pub resource Vault: Provider, Receiver, Balance { - access(Provider) fun withdraw(amount: UFix64): @Vault { + access(Withdraw) fun withdraw(amount: UFix64): @Vault { // ... } pub fun deposit(from: @Vault) { @@ -285,9 +399,9 @@ pub resource Vault: Provider, Receiver, Balance { ``` 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` reference, but because this reference is not entitled to `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) &Vault` and call `withdraw`. +They would also be able to cast this to a `&Vault` reference, but because this reference is not entitled to `Withdraw`, +they would be unable to call the `withdraw` function. However, if a user possessed a `auth(Withdraw) &{Balance}` reference, +they would be able to cast this to `auth(Withdraw) &Vault` and call `withdraw`. ### Compatibility @@ -300,7 +414,7 @@ vulnerable to having `pub` fields accessed from now-downcastable references. There is a huge user impact to rolling out this change; every single contract would become invalid, and would need to be manually audited by the authors in order to be used. This is because all code previously written using the old model of access control (wherein giving someone a `&{Balance}` would prevent them from calling `pub` functions on `Provider` like `withdraw`), would become invalidated, allowing everyone -to call `pub` members like `withdraw` unless those methods were updated to be `access(Provider)`. +to call `pub` members like `withdraw` unless those methods were updated to be `access(Withdraw)`. ### Rollout @@ -308,9 +422,9 @@ There are two cases that need handling to migrate to this new paradigm: contract Existing references in storage (i.e. `Capability` values) would need to be migrated, as they would no longer be semantically valid under the new system. The simplest way to do this would be to ensure existing capabilities stay usable by converting existing capabilities and links' reference types to `auth` references, -e.g. `Capability<&Vault{Provider}>` -> `Capability`. Specifically, all existing references would become `auth` with regard to their entire borrow type, +e.g. `Capability<&Vault{Withdraw}>` -> `Capability`. Specifically, all existing references would become `auth` with regard to their entire borrow type, as this does not add any additional power to these `Capability` values that did not previously exist. For example, `&Vault{Provider}` previously had the ability to call `withdraw`, -which was `pub` on `Provider`. After this change, it will still have the ability to call `withdraw`, as it is now `access(Provider)` on `Provider`, but the reference now has `auth(Provider)` +which was `pub` on `Provider`. After this change, it will still have the ability to call `withdraw`, as it is now `access(Withdraw)` on `Provider`, but the reference now has `auth(Withdraw)` access to that type. However, this does not handle the previously mentioned problem wherein existing contracts become vulnerable to exploitation, as all their `pub` functions would become @@ -350,75 +464,18 @@ FLIP's proposal individual functions can be declared with `auth` access or `pub` creator of the reference type) to exert more fine-grained control over what is accessible. By contrast, the implementor of the concrete type has little to no say in the access-control on their value beyond selecting which interfaces they wish to conform to. -## Questions and Discussion Topics - -* The upcoming proposed changes to permit interfaces to conform to other interfaces will necessarily change the subtyping rules -for `auth` references, as it would no longer be sufficient to compare the sets for the two `auth` references just based on membership. -The membership check would still exist, but it would function as a width subtyping rule, in addition to a depth rule based on -interface chains. I.e. it would be the case that for two auth references, `auth(U) &X <: auth(T1) &X`, if `U <: T`. - - We can combine this depth rule with the already existing width rule to yield a combined subtyping rule: `auth{U1, U2, ... } &X <: auth{T1, T2, ... } &X`, -whenever `∀T ∈ {T1, T2, ... }, ∃U ∈ {U1, U2, ... }, U <: T`. +## Prior Art - So, for example, given an interface heirarchy: +`entitlement`s function similarly to facets in the Object Capabilities model. - ```cadence - pub resource interface A {} - pub resource interface B {} - pub resource interface C: A {} - pub resource interface D: B {} - pub resource interface E: C, D {} - pub resource R: E {} - ``` - - we would have `auth(E) &R <: auth(C) &R <: auth(A) &R <: &R` and `auth(E) &R <: auth(D) &R <: auth(B) &R <: &R`. - It would also be the case that `auth(D, C) &R <: auth(A) &R` as well, because `C <: A`, and that `auth(E) &R <: auth(B, C) &R`, - because `E <: B` and `E <: C`. +## Questions and Discussion Topics * One potential change this unlocks would be to restrict the creation of Capabilities based on the entitlements they contain. We could restrict the `public` domain to be only for non-entitled Capabilities, while the `private` domain would be only for `auth`-references, which would prevent accidentally allowing anybody to get access to your `withdraw` function, for example. -* After this change, it is unclear whether restricted type would still have a place in the type system of Cadence. Consider that -now, with interface types specified both in the `auth` portion of the reference as well in the actual referenced type, there is an -element of redundancy in a type like `auth(T) &{T}` which is unnecessary. Users will be likely to instead specify reference with something like -`let ref = &r as auth(Provider) &Vault` instead of `let ref = &r as auth(Provider) &{Provider, Receiver}` given that downcasting is now possible. - - Restricted types would now only really be used as "interface sets", rather than restrictions, to specify that that a type should have a certain set - of functionality; e.g. writing a function that operates over all `Provider` types, for example. We can support this use case without restricted types, - however, the outside part `T` of the restricted type `T{Us}` is no longer necessary. One improvement we could make as part of this change would be to - remove "restricted types" and replace them with a "interface set" or "intersection" type that only contains the `{Us}`. - -* One large open question about this is whether entitlements should be a separate language object from interfaces. The current proposal allows users to use -interfaces as entitlements, since currently in Cadence one of the functions of interfaces is to limit access the way that entitlements will do. However, -there isn't any strong theoretical link between the polymorphism behavior of interfaces and this access control functionality, so it might be possible to -separate these concepts and allow declaring entitlements separately. An example of such functionality might look like this: - - ```cadence - contract FungibleToken { - resource interface Provider { - entitlement withdraw - - access(withdraw) fun withdraw(amount: UFix64): @Vault {} - } - - resource interface Receiver { - access(all) fun deposit(from: @Vault) {} - } - - resource interface Balance { - access(all) fun balance(): UFix64 {} - } - - resource interface Vault: Provider, Receiver, Balance {} - } - - resource FlowVault: FungibleToken.Vault { - access(FungibleToken.Provider.withdraw) fun withdraw(amount: UFix64): @Vault {} - access(all) fun deposit(from: @Vault) {} - access(all) fun balance(): UFix64 {} - - access(class) fun internalUtilityFunction(otherVault: auth(FungibleToken.Provider.withdraw)& FlowVault) {} - access(FungibleToken.Provider.withdraw) fun withdrawAll(): @Vault {} - } - ``` \ No newline at end of file +* We've discussed the value of being able to declare entitlements in terms of existing entitlements. I.e. if I have +some entitlement `A` and `B`, it would be useful to be able to declare `C` as the intersection of `A` and `B` or the union. Additionally +it would be valuable to be able to declare `C` as a subset of `A` or `B`'s functions to reduce code repetition. However, this is not a +necessary part of the initial implementation of this FLIP, as it can be added later in a non-breaking way. As such this feature is +out of scope for this FLIP. \ No newline at end of file From 8dba17554f3650c201bc381f61266c17e617cf67 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 21 Feb 2023 11:09:40 -0500 Subject: [PATCH 18/30] update FLIP to improve subtyping model for interfaces --- cadence/2022-12-14-auth-remodel.md | 66 ++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index d1c81b28..98c45f94 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -52,7 +52,7 @@ with a few key differences. They use the following syntax: `pub? entitlement Date: Thu, 9 Mar 2023 10:58:55 -0500 Subject: [PATCH 19/30] add info about conjuctive and disjunctive entitlement sets --- cadence/2022-12-14-auth-remodel.md | 110 ++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 11 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 98c45f94..686cdb72 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -3,7 +3,7 @@ status: draft flip: NNN (do not set) authors: Daniel Sainati (daniel.sainati@dapperlabs.com) sponsor: Daniel Sainati (daniel.sainati@dapperlabs.com) -updated: 2023-02-17 +updated: 2023-03-09 --- # Entitlements @@ -110,9 +110,45 @@ resource R { } ``` -A single member definition can include multiple entitlements; in which case the member is accessible to any `auth` reference with any -of those entitlements. As such, that member's definition must match all of its definitions in each of the `entitlement` declarations being -used. +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 conjection set (or an "and"); it is accessible only to an `auth` reference with all of those entitlements. + +So, for example, in + +```cadence +entitlement E { + fun foo() {} + fun bar() {} +} +entitlement F { + fun foo() {} + fun bar() {} +} +resource R { + access(E, F) foo() {} + access(E | F) bar() {} +} +``` + +`foo` is only calleable on a reference to `R` that is `auth` for both `E` and `F`, while `bar` is calleable on any `auth` reference that is `auth` for either `E` or `F` (or both). + +In either case, a member's definition must match all of its definitions in each of the `entitlement` declarations being used in the access modifier for that member, so in the below case: + +```cadence +entitlement E { + fun bar(a: A) {} +} +entitlement F { + fun bar() {} +} +resource R { + access(E | F) bar(a : A) {} +} +``` + +This would fail to type check because `bar`'s type does not match its definition in `F`. Additionally, because we only check that each member using an `entitlement` matches that entitlement's declaration of the member, it is not necessary for a composite or interface to use every single member on an `entitlement` if they do not want or need to. So, for example, this code is valid: @@ -200,7 +236,7 @@ pub resource R: I { since if this were to typecheck, anybody with a `&R` reference could upcast it to `&{I}` and thus gain the ability to call `foo`. -When multiple interfaces declare the same function with different entitlements, a composite implementing both interfaces must use the union of the function's entitlement +When multiple interfaces declare the same function with different entitlements, a composite implementing both interfaces must use the `|` union of the function's entitlement sets as the access modifier for that function. E.g. ```cadence @@ -219,7 +255,7 @@ pub resource interface G { } pub resource R: I, G { - access(E, F) fun foo() {} // valid, access(E, F) permits both access(E) and access(F) + access(E | F) fun foo() {} // valid, access(E | F) permits both access(E) and access(F) } pub resource S: I, G { access(E) fun foo() {} // invalid, does not match definition in G @@ -305,7 +341,7 @@ entitlement would remain present on the dynamic (run-time) type of this referenc The other part of this change is to remove the limitations on resource downcasting that used to exist. Prior to this change, non-`auth` references could not be downcast at all, since the sole purpose of the `auth` keyword was to indicate that references could be downcast. With the proposed change, all reference types can be downcast or upcast the same way any other type would be. So, for example -`&{A} as! &R` would be valid, as would `&AnyResource as? &{B, C}` or `&{A} as? &{B}`, using the hierarchy defined above. +`&{I} as! &R` would be valid, as would `&AnyResource as? &{I}` or `&{I} as? &{J}`, using the hierarchy defined above (for any `J`). However, the `auth`-ness (and the reference's set of entitlements) would not change on downcasting, nor would that set be expandable via casting. The subtyping rules for `auth` references is that `auth (U1, U2, ... ) &X <: auth (T1, T2, ... ) &X` whenever `{U1, U2, ...}` @@ -329,15 +365,67 @@ let ref2 = &r as auth(A, B, C) &R `foo` would return `true` when called with `ref2`, because the runtime type of `ref` in the failable cast is a subtype of `auth(A, B) &R`, since `{A, B, C}` is a superset of `{A, B}`, but would return `false` when called with `ref1`, since `{A}` is not a superset of `{A, B}`. +In addition to the `,`-separated list of entitlements (which defines a conjunction/"and" set for `auth` modifiers similarly to its behavior for `access` modifiers), it is also possible, +although very rarely necessary, to define `|`-separated entitlement lists in `auth` modifers for references, like so: `auth(E1 | E2 | ...) &T`. In this case, the type denotes that the +reference to `T` is authorized for **at least one** of the entitled specified, not that it is `auth` for all of them. This means, for example, that an `auth(A | B) &R` reference +would not be permitted to call an `access(A)` function on `R`, because we only know that the reference is authorized for one of `A` **or** `B`, and cannot guarantee that it is permitted to +call an `A`-entitled function. However, an `auth(A | B) &R` reference could call an `access(A | B)` function on `R`, because the function requires one of `A` or `B`, and we know +that our reference has at least one of these entitlements. + +```cadence +entitlement E { + fun foo() + fun bar() + fun baz() +} +entitlement F { + fun foo() + fun bar() + fun qux() +} +resource R { + access(E | F) fun foo() { ... } + access(E, F) fun bar() { ... } + access(E) fun baz() { ... } + access(F) fun qux() { ... } +} +fun test(ref: auth(E | F) &R) { + ref.foo() // allowed because `foo` requires either `E` or `F` + ref.bar() // not allowed because the reference may not have `E` and `F` + ref.baz() // not allowed because the reference may not have `E` + ref.qux() // not allowed because the reference may not have `F` + (ref as? auth(E) &R)?.baz() // allowed statically, will succeed at runtime if `ref` has an `E` entitlement + (ref as? auth(F) &R)?.baz() // allowed statically, will succeed at runtime if `ref` has an `F` entitlement + (ref as? auth(E, F) &R)?.qux() // allowed statically, will succeed at runtime if `ref` has both an `E` and an `F` entitlement +} +``` + +The subtyping rules for `|`-separated entitlement lists allow lists to expand on supertyping. I.e., `auth(A | B) &R <: auth(A | B | C) &R`, as this decreases the information we have about +the reference's entitlements and thus permits fewer operations. In general, `auth (U1 | U2 | ... ) &X <: auth (T1 | T2 | ... ) &X` whenever `{U1, U2, ...}` +is a subset of `{T1, T2, ...}`, or equivalently `∀U ∈ {U1, U2, ...}, ∃T ∈ {T1, T2, ...}, T = U`. To shrink the `|`-separated entitlement list, downcasting is required (as shown in the example above). + +`,`-separated entitlement lists subtype `|`-separated ones as long as the two sets are not disjoint; that is, as long as there is an entitlement in the subtype set that is also in the supertype set. +This is because we are casting from a type that is known to possess all of the listed entitlements to a type that is only guaranted to possess one. More specifically, +`auth (U1, U2, ... ) &X <: auth (T1 | T2 | ... ) &X` whenever `{U1, U2, ...}` is not disjoint from `{T1, T2, ...}`, or equivalently `∃U ∈ {U1, U2, ...}, ∃T ∈ {T1, T2, ...}, T = U`. + +In practice `|`-separated entitlement lists never subtype `,`-separated ones except in the trivial case. This is because we are attempting to upcast (change the type without gaining any new +specificity) from a reference where we only know we possess at least one of the listed entitlement to one where we know we possess all of them. This is only possible when all of the references +in both lists are equal. More specifically, `auth (U1, U2, ... ) &X <: auth (T1 | T2 | ... ) &X` whenever `∀U ∈ {U1, U2, ...}, ∀T ∈ {T1, T2, ...}, T = U`. +As one can see, this is only possible when every `U` and `T` are the same entitlement, or when the two entitlement lists each only have a single equivalent element. + #### Attachments and Entitlements Attachments would interact with entitlements and access-limited members in a nuanced but intuitive manner, where the attachment's entitlements are implicitly parameterized over the entitlements of its `base` value. Attachments would remain `pub` accessible, but would permit the declaration of `access`-limited members. The `base` value, which currently is simply a `&R` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have its entitlements -depend on the entitlements of the member function in question. In a member declaration `access(X) fun foo()` in `A`, the `base` variable would have -type `auth(X) &R`, while in a member declaration in `A` `pub fun bar()`, `base` would just be an `&R`. This would effectively mean that the `access(X)` -members of the `base` would only be available to the attachment author in `auth(X)` members on the attachment. Similarly, in an `access(X)` -member, the `self` reference of the attachment `A` would be `auth(X) &A`, while in a `pub`-access member it would just be `&A`. +depend on the entitlements of the member function in question. In a member declaration `access(X | Y) fun foo()` in `A`, the `base` variable would have +type `auth(X | Y) &R`, while in a member declaration in `A` `pub fun bar()`, `base` would just be an `&R`. This would effectively mean that the `access(X)` +members of the `base` would only be available to the attachment author in `access(X)` members on the attachment. Similarly, in an `access(X, Y)` +member, the `self` reference of the attachment `A` would be `auth(X, Y) &A`, while in a `pub`-access member it would just be `&A`. + +One important point to note here is that the previously mentioned rules about `auth(...) &T` reference types (namely that the set of entitlements on the `auth` modifier must all be valid for the referenced type `T`) require that attachments for any `base` type only use entitlements that are already present on that `base`. +To see why, consider an attachment `A` declared `attachment A for R` with a member `access(X) fun foo()`. Within the body of `foo`, using the rules described above, the +`base` reference would have type `auth(X) &R`. This type is only reasonable if `X` is a valid entitlement for `R`. This would then be combined with a change to the attachment access rules: rather than `v[A]` always returning an `&A?` value, the type of the returned attachment reference would depend on the type of `v`. If `v` is not a reference, then any access `v[A]` would be fully authorized with type `(auth(owner) &A)?` (or similar From b959350d80e24d9f5a209e3571acf61ec8bba714 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 14 Mar 2023 13:40:20 -0400 Subject: [PATCH 20/30] add sections about entitlement mappings --- cadence/2022-12-14-auth-remodel.md | 408 ++++++++++++++++++----------- 1 file changed, 248 insertions(+), 160 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 686cdb72..a015178f 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -3,7 +3,7 @@ status: draft flip: NNN (do not set) authors: Daniel Sainati (daniel.sainati@dapperlabs.com) sponsor: Daniel Sainati (daniel.sainati@dapperlabs.com) -updated: 2023-03-09 +updated: 2023-03-14 --- # Entitlements @@ -47,69 +47,28 @@ method like `withdraw`, the user would need to explicitly create the Capability ### Entitlements -The first part of this FLIP proposes to add a new declaration type to Cadence: `entitlement`s. `entitlement`s are declared similarly to interfaces, but -with a few key differences. They use the following syntax: `pub? entitlement { ... }`, where the body of the `entitlement` contains a list of -functions or fields like in an interfaces. However, it is important to note three key distinctions: - -1) `entitlement`s are not kinded the way that interfaces are; they are neither resources nor structs, and as such cannot appear in any type position where a -kinded type is expected. In practice, this means entitlement annotations can only be used inside of the `auth` portion of references (described below). - -2) `entitlement` functions cannot contain default implementations, pre-conditions, or post-conditions. `entitlement`s are used purely for access control, and -contain no polymorphism functionality like interfaces do. - -3) `entitlement` members are not declared with an access modifier the way that interface members are; this is because the `entitlement` is used to define -the access of these members on composites and interfaces, and thus it would be nonsensical for the entitlement definition itself to contain an access modifier. - -As such, the following is a valid entitlement definition: +The first part of this FLIP proposes to add a new declaration type to Cadence: `entitlement`s. `entitlement` declarations are simple, +just the keyword `entitlement` and the name; we only require that they be pre-declared to guard against typos and other minor errors. +A sample `entitlement` may look like this: ```cadence -pub entitlement E { - fun foo(a: Int) - let x: String -} +entitlement E ``` -while the following are not: - -```cadence -pub entitlement A { - pub fun foo(a: Int) // cannot have an access modifier -} -pub resource entitlement A { // entitlement is not kinded - fun foo(a: Int) -} -pub entitlement A { - fun foo(a: Int) {} // cannot have a body -} -``` +In the future, this is easily extensible to allow creating entitlements that are constructed from others via operators like `&` or `|`. ### Entitlement-access fields To go with these `entitlement` declarations, this FLIP proposes to add a new access control modifier to field and function declarations in composite types: `access(X)`, which allows access to either the immediate owner of the resource (i.e. anybody who has the actual resource value), -or someone with an `auth(X)` reference to the type on which the member is defined. The `X` here can be the qualified name of any entitlement, but -the function or field definition that uses that access modifier must match its definition in the `X` entitlement. So, this would be allowed: +or someone with an `auth(X)` reference to the type on which the member is defined. The `X` here can be the qualified name of any entitlement, e.g.: ```cadence -entitlement E { - fun foo(a: Int) -} +entitlement E resource R { access(E) fun foo(a: Int) { } } ``` - -while this would not: - -```cadence -entitlement E { - fun foo(a: Int) -} -resource R { - access(E) fun foo(a: String) { } // definition does not match `foo`'s definition in `E`. -} -``` - 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. @@ -118,14 +77,8 @@ An entitlement list defined using a `,` functions like a conjection set (or an " So, for example, in ```cadence -entitlement E { - fun foo() {} - fun bar() {} -} -entitlement F { - fun foo() {} - fun bar() {} -} +entitlement E +entitlement F resource R { access(E, F) foo() {} access(E | F) bar() {} @@ -134,38 +87,6 @@ resource R { `foo` is only calleable on a reference to `R` that is `auth` for both `E` and `F`, while `bar` is calleable on any `auth` reference that is `auth` for either `E` or `F` (or both). -In either case, a member's definition must match all of its definitions in each of the `entitlement` declarations being used in the access modifier for that member, so in the below case: - -```cadence -entitlement E { - fun bar(a: A) {} -} -entitlement F { - fun bar() {} -} -resource R { - access(E | F) bar(a : A) {} -} -``` - -This would fail to type check because `bar`'s type does not match its definition in `F`. - -Additionally, because we only check that each member using an `entitlement` matches that entitlement's declaration of the member, it is not -necessary for a composite or interface to use every single member on an `entitlement` if they do not want or need to. So, for example, this code is valid: - -```cadence -entitlement E { - fun foo(a: A) {} - fun bar(b: B) {} -} -resource R { - access(E) foo(a: A) {} -} -``` - -because `foo`'s definition matches its definition in `E`. There is no check that `R` implmements all the members in `E`, however. This does not -pose an issue for type safety because `entitlement`s are never used for polymorphism, only access control. - Like `access(contract)` and `access(account)`, this new modifier sits exactly between `pub` and `priv` (or equivalently `access(self)`) in permissiveness; it allows less access than `pub`, but strictly more than `priv`, as an `access(X)` field or function can be used anywhere in the implementation of the composite. To see why, consider that `self` is necessarily of the same type as the composite, meaning @@ -174,7 +95,7 @@ that the access rules defined above allow any `access(I)` members to be accessed As such, the following would be prohibited statically: ```cadence -pub entitlement E {} +pub entitlement E pub resource R { access(E) fun foo() { ... } } @@ -185,7 +106,7 @@ r.foo() while all of these would be permitted: ```cadence -pub entitlement E {} +pub entitlement E pub resource R { access(E) fun foo() { ... } pub fun bar() { @@ -203,9 +124,7 @@ composite member, as this is less restrictive, in order to prevent users from ac As such, the below code would not typecheck. ```cadence -pub entitlement E { - fun foo() -} +pub entitlement E pub resource interface I { access(E) fun foo() @@ -221,9 +140,7 @@ If users would like to expose an access-limited function to `pub` users, they ca As with normal subtyping, this would also be statically rejected: ```cadence -pub entitlement E { - fun foo() -} +pub entitlement E pub resource interface I { pub fun foo() @@ -240,16 +157,12 @@ When multiple interfaces declare the same function with different entitlements, sets as the access modifier for that function. E.g. ```cadence -pub entitlement E { - fun foo() -} +pub entitlement E pub resource interface I { access(E) fun foo() } -pub entitlement F { - fun foo() -} +pub entitlement F pub resource interface G { access(F) fun foo() } @@ -274,12 +187,8 @@ in the parentheses denote the entitlements. This permits these references to acc So, for example, given two entitlement definitions and a composite definition: ```cadence -pub entitlement A { - fun foo() -} -pub entitlement B { - fun bar() -} +pub entitlement A +pub entitlement B pub resource R { access(A) fun foo() { ... } access(B) fun bar() { ... } @@ -293,12 +202,8 @@ it does not have an entitlement for `B`. However, `baz` would be accessible on t A reference type's entitlements must be valid entitlements of the referenced type: it is nonsensical, given a set of definitions like: ```cadence -pub entitlement A { - fun foo() -} -pub entitlement B { - fun bar() -} +pub entitlement A +pub entitlement B pub resource R { access(A) fun foo() { ... } } @@ -315,12 +220,8 @@ would fail statically. It is important to note, however, that because the type o permit more entitlements than the static type. Consider the following code: ```cadence -pub entitlement A { - fun foo() -} -pub entitlement B { - fun bar() -} +pub entitlement A +pub entitlement B pub resource interface I { access(A) fun foo() } @@ -373,16 +274,8 @@ call an `A`-entitled function. However, an `auth(A | B) &R` reference could call that our reference has at least one of these entitlements. ```cadence -entitlement E { - fun foo() - fun bar() - fun baz() -} -entitlement F { - fun foo() - fun bar() - fun qux() -} +entitlement E +entitlement F resource R { access(E | F) fun foo() { ... } access(E, F) fun bar() { ... } @@ -413,35 +306,232 @@ specificity) from a reference where we only know we possess at least one of the in both lists are equal. More specifically, `auth (U1, U2, ... ) &X <: auth (T1 | T2 | ... ) &X` whenever `∀U ∈ {U1, U2, ...}, ∀T ∈ {T1, T2, ...}, T = U`. As one can see, this is only possible when every `U` and `T` are the same entitlement, or when the two entitlement lists each only have a single equivalent element. +### Entitlement Mapping and Nested Values + +When objects have reference fields to child objects, it can often be valuable to have different views of that reference depending on the entitlements one has on the reference to the parent object. +Consider the following example: + +```cadence +entitlement OuterEntitlement +entitlement SubEntitlement + +resource SubResource { + pub fun foo() { ... } + access(SubEntitlement) fun bar() { ... } +} + +resource OuterResource { + pub let pubRef: &SubResource + access(OuterEntitlement) let entitledRef: auth(SubEntitlement) &SubResource + + init(ref: auth(SubEntitlement) &SubResource) { + self.pubRef = ref // `ref` is implicitly upcast here to `&SubResource` + self.entitledRef = ref + } +} +``` + +With this pattern, we can store a reference to a `SubResource` on an `OuterResource` value, and create different ways to access that nested resource depending on the entitlement one +posseses. Somoneone with only an unauthorized reference to `OuterResource` can only access the `pubRef` field, and thus can only get an unauthorized reference to `SubResource` that lets them call `foo`. +However, someone with a `OuterEntitlement`-authorized refererence to the `OuterResource` can access the `entitledRef` field, giving them a `SubEntitlement`-authorized reference to `SubResource` that +allows them to call `bar`. + +This pattern is functional, but it is unfortunate that we are forced to "duplicate" the reference to `SubResource`, storing it twice on the object in differently named fields, essentially creating +two different views to the same object that are stored as different fields. To avoid necessitating this duplication, we add support to the language for "entitlement mappings", a way to declare +statically how entitlements are propagated from parents to child objects in a nesting hierarchy. So, the above example could be equivalently written as: + +```cadence +entitlement OuterEntitlement +entitlement SubEntitlement + +// specify a mapping for entitlements called `Map`, which defines a function +// from an input set of entitlements (called the domain) to an output set (called the image) +entitlement mapping Map { + OuterEntitlement -> SubEntitlement +} + +resource SubResource { + pub fun foo() { ... } + access(SubEntitlement) fun bar() { ... } +} + +resource OuterResource { + // by referering to `Map` here, we declare that the entitlements we receive when accessing the `ref` field on this resource + // will depend on the entitlements we possess to the resource during the access. + access(Map) let ref: auth(Map) &SubResource + + init(ref: auth(SubEntitlement) &SubResource) { + // because the `self.ref` field here is typed with a mapping, in order to assign to it the rhs of the assignment must be fully-entitled + // for the range of this mapping, that is, it must possess all of the entitlements in the image of `Map` + self.ref = ref + } +} + +// given some value `r` of type `@OuterResource` +let pubRef = &r as &OuterResource +let pubSubRef = r.ref // has type `&SubResource` + +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` +``` + +Entitlement mappings do not need to be 1:1; it is perfectly valid to define a mapping like so: + +```cadence +entitlement mapping M { + A -> C + B -> C + A -> D +} +``` + +If this mapping were used to define the `auth` access of a nested resource reference, one could obtain a `C`-entitled reference to that nested resource with either an +`A` or a `B` entitled outer reference. Conversely, an `A`-entitled outer reference would yield a nested reference entitled for both `C` and `D`. Specifically, if the +field with the nested reference were called `foo`, then we'd get these different outputs for differently typed `ref` inputs: +* if `ref` was typed as `&Outer`, then `ref.foo` would just be an `&Inner` +* if `ref` was typed as `auth(A) Outer`, then `ref.foo` would be `auth(C, D) &Inner`, since `A` maps to both of these outputs +* if `ref` was typed as `auth(B) Outer`, then `ref.foo` would be `auth(C) &Inner`, since `B` maps only to `C` +* if `ref` was typed as `auth(A | B) Outer`, then `ref.foo` would be `auth(C) &Inner`, since `C` is mapped to by both of these inputs + +Note, however, that because the `|` and `,` typed entitlement sets cannot be mixed (this is a restriction for simplicity more than anything else), it is not possible to use non 1:1 +mappings in situations where their output would be an unrepresentable entitlement set. So with this mapping: + +```cadence +entitlement mapping M { + E -> A + E -> B + F -> C + F -> D +} +``` +We would have the following access relationship: + +* if `ref` was typed as `&auth(E) Outer`, then `ref.foo` would be `auth(A, B) &Inner` +* if `ref` was typed as `&auth(F) Outer`, then `ref.foo` would be `auth(C, D) &Inner` +* if `ref` was typed as `&auth(E, F) Outer`, then `ref.foo` would be `auth(A, B, C, D) &Inner` +* However, if `ref` was typed as `&auth(E | F) Outer`, then `ref.foo` would result in a static error, as this would be `auth((A, B) | (C, D)) &Inner`, which is not representable in Cadence + +It is also important to note that when using a mapping, when the mapped field is initialized, it must be initialized with a concrete reference value that is fully-entitled +to the output of the mapping. So, given the following mapping: + +``` +entitlement mapping Map { + A -> B + C -> D + C -> E +} + +resource SubResource { ... } + +resource OuterResource { + access(Map) let ref: auth(Map) &SubResource + init(ref: auth(B) &SubResource) { + self.ref = ref // this would fail, as `ref` is only entitled to `B` + } +} +``` + +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. + #### Attachments and Entitlements -Attachments would interact with entitlements and access-limited members in a nuanced but intuitive manner, where the attachment's entitlements -are implicitly parameterized over the entitlements of its `base` value. Attachments would remain `pub` accessible, but would permit the declaration of `access`-limited members. -The `base` value, which currently is simply a `&R` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have its entitlements -depend on the entitlements of the member function in question. In a member declaration `access(X | Y) fun foo()` in `A`, the `base` variable would have -type `auth(X | Y) &R`, while in a member declaration in `A` `pub fun bar()`, `base` would just be an `&R`. This would effectively mean that the `access(X)` -members of the `base` would only be available to the attachment author in `access(X)` members on the attachment. Similarly, in an `access(X, Y)` -member, the `self` reference of the attachment `A` would be `auth(X, Y) &A`, while in a `pub`-access member it would just be `&A`. +Attachments would interact with entitlements and access-limited members in a nuanced but intuitive manner, using the entitlement mapping feature described above. Instead +of requiring that all attachment declarations are `pub`, they can additionally be declared with an `access(X)` modifier, where `X` is the name of an entitlement mapping. +When declared with an entitlement mapping access, the attachment's entitlements are propagated from the entitlements of its `base` value according to the mapping. Attachments would remain `pub` accessible, but would permit the declaration of `access`-limited members. So, for example, given some declarations like: + +```cadence +entitlement E +entitlement F +entitlement mapping M { + E -> F +} +access(M) attachment A for R {} +``` + +given a reference to `r` called `ref`, when `ref` has type `&R` then `ref[A]` has type `&A?`, while when `ref` has type `auth(E) &R` then `ref[A]` has type `auth(F) &A?`. Additionally, as +owned values are considered fully entitled, accessing `A` directly off of an `@R`-typed value `r` will yield a reference to `A` that is fully entitled, that is, an `A` reference that +is authorized for the entire image of `M`. + +Within the declaration of the attachment itself, the members of the attachments can use any entitlements that exist in the image of `M`, but cannot use other entitlements as the +attachment access semantics would make it impossible to obtain a reference to the attachment with entitlements not in the image of `M`. Additionally, there are new semantics for inferring +the type of `self` and `base` in the bodies of attachment member functions. -One important point to note here is that the previously mentioned rules about `auth(...) &T` reference types (namely that the set of entitlements on the `auth` modifier must all be valid for the referenced type `T`) require that attachments for any `base` type only use entitlements that are already present on that `base`. -To see why, consider an attachment `A` declared `attachment A for R` with a member `access(X) fun foo()`. Within the body of `foo`, using the rules described above, the -`base` reference would have type `auth(X) &R`. This type is only reasonable if `X` is a valid entitlement for `R`. +The `self` value, which currently is simply a `&A` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have its entitlements +depend on the entitlements of the member function in question. In a member declaration `access(X | Y) fun foo()` in `A`, the `self` variable would have +type `auth(X | Y) &R`, while in a member declaration in `A` `pub fun bar()`, `self` would just be an `&R`. -This would then be combined with a change to the attachment access rules: rather than `v[A]` always returning an `&A?` value, the type of the returned -attachment reference would depend on the type of `v`. If `v` is not a reference, then any access `v[A]` would be fully authorized with type `(auth(owner) &A)?` (or similar -super-entitlement keyword). If `v` is a reference, then the access `v[A]` would return a reference to `A` with the same set of entitlements as `v`. This would prevent -the attachment from accessing `auth` members on its `base` unless the specific instance of that base to which it is attached has the proper entitlement. +Meanwhile, the `base` value would have slightly more complex semantics - specifically, on an attachment `access(M) attachment A for R`, +within a member function declared with some entitlement set `S`, within that +member function the `base` reference would be typed with `auth(P) &R`, where `P` is the preimage of `S` under the mapping `M`. +This can be understood more easily when we take a concrete example; given the following declarations: -So, for example, given the following declaration: ```cadence -entitlement Withdraw { - fun withdraw(_ amount: UFix64): @Vault +entitlement E +entitlement F +entitlement X +entitlement Y +entitlement mapping M { + E -> F + X -> Y +} +access(M) attachment A for R { + access(F) fun foo() { ... } + access(Y) fun bar() { ... } + access(F, Y) fun baz() { ... } + access(F | Y) fun qux() { ... } } +``` + +As explained previously, within `foo` the `self` reference has type `auth(F) &A`, since `foo` is only callable when one possesses at least an `F`-entitled +reference to `A`. However, when considered carefully it also becomes apparent that the given the specified mapping in `M`, the only way to obtain such a reference +is via access on an `E`-entitled reference to `R`. Specifically, an `F`-entitled reference to `A` can only be accessed on a `base` that possesses an entitlement for `E`. +As such, we can thus infer that within `foo` the `base` reference has an `auth(E) &R` type. The same logic can be used to infer that within `bar`, `base` must have an +`auth(X) &R` type. Just as we called the output of `M` its "image", the output of the "inverted" version of `M` is called its "preimage". + +For more complex access modifiers, instead of computing `M`'s preimage for a single element, we compute the preimage for the entire set. I.e., within `baz` +the `base` reference would need to have type `auth(E, X) &R` in order to make `baz` callable on an `A` reference, and within `qux` it would need a +`auth(E | X) &R` type in order to make its usage safe. Specifically in the second case, because `qux` is callable with either an `F` or a `Y`-entitled reference to `A`, +we can only deduce that `base` had either an `E` or an `X` entitled reference to `R`. + +As before with regular nested objects, certain non-one-to-one mappings make inferring the `base` type impossible. In the following example: + +```cadence +entitlement E +entitlement F +entitlement X +entitlement Y +entitlement Z +entitlement mapping M { + E -> X + E -> Y + F -> Z +} +access(M) attachment A for R { + access(F | Y) fun foo() { ... } +} +``` + +The preimage of `F | Y` under `M` would be `(X, Y) | Z`. It is impossible to create an `auth` reference with this entitlement set, so the `base` reference +would not be typable within the body of `foo`. To avoid this problem, we will simply prevent defining functions with impossible preimages, so non-one-to-one entitlement +mappings will require careful use for attachments. + +Putting all of these rules together, we can see that given the following declaration: +```cadence +entitlement Withdraw +entitlement ConvertAndWithdraw + +entitlement mapping ConverterMap { + Withdraw -> ConvertAndWithdraw +} + interface Provider { access(Withdraw) fun withdraw(_ amount: UFix64): @Vault } -attachment CurrencyConverter for Provider { +access(ConverterMap) attachment CurrencyConverter for Provider { pub fun convert(_ amount: UFix64): UFix64 { // ... } @@ -451,9 +541,9 @@ attachment CurrencyConverter for Provider { return <-vault } - access(Withdraw) fun withdraw(_ amount: UFix64): @Vault { + access(ConvertAndWithdraw) fun withdraw(_ amount: UFix64): @Vault { let convertedAmount = self.convert(amount) - // this is permitted because this function has an entitlement to `Withdraw` + // this is permitted because this function has an entitlement to `ConvertAndWithdraw` // on the attachment, and thus `base` has type `auth(Withdraw) &{Provider}` return <-base.withdraw(amount: amount) } @@ -485,7 +575,7 @@ attachment CurrencyConverter for Provider { let vault <- attach CurrencyConverter() to <-create Vault(balance: /*...*/) let authVaultReference = &vault as auth(Withdraw) &Vault -let converterRef = authVaultReference[CurrencyConverter]! // has type auth(Withdraw) &CurrencyConverter, can call `withdraw` +let converterRef = authVaultReference[CurrencyConverter]! // has type auth(ConvertAndWithdraw) &CurrencyConverter, can call `withdraw` let otherVaultReference = &vault as &Vault let otheConverterRef = otherVaultReference[CurrencyConverter]! // has type &CurrencyConverter, cannot call `withdraw` @@ -507,9 +597,7 @@ entitlement sets to control access. With this new design, the `Vault` heirarchy might be written like so: ```cadence -pub entitlement Withdraw { - fun withdraw(amount: UFix64): @Vault -} +pub entitlement Withdraw pub resource interface Provider { access(Withdraw) fun withdraw(amount: UFix64): @Vault { @@ -561,11 +649,11 @@ to call `pub` members like `withdraw` unless those methods were updated to be `a There are two cases that need handling to migrate to this new paradigm: contracts and data. Existing references in storage (i.e. `Capability` values) would need to be migrated, as they would no longer be semantically valid under the new system. -The simplest way to do this would be to ensure existing capabilities stay usable by converting existing capabilities and links' reference types to `auth` references, -e.g. `Capability<&Vault{Withdraw}>` -> `Capability`. Specifically, all existing references would become `auth` with regard to their entire borrow type, -as this does not add any additional power to these `Capability` values that did not previously exist. For example, `&Vault{Provider}` previously had the ability to call `withdraw`, -which was `pub` on `Provider`. After this change, it will still have the ability to call `withdraw`, as it is now `access(Withdraw)` on `Provider`, but the reference now has `auth(Withdraw)` -access to that type. +The simplest way to do this would be to ensure existing capabilities stay usable by converting existing capabilities and links' reference types to `auth` references, along with creating +entitlements that map 1:1 with existing interfaces, e.g. `Capability<&Vault{Withdraw}>` -> `Capability`. Specifically, all existing +references would become `auth` with regard to their entire borrow type, as this does not add any additional power to these `Capability` values that did not previously exist. +For example, `&Vault{Provider}` previously had the ability to call `withdraw`, which was `pub` on `Provider`. After this change, it will still have the ability to call `withdraw`, +as it is now `access(Withdraw)` on `Provider`, but the reference now has `auth(Withdraw)`access to that type. However, this does not handle the previously mentioned problem wherein existing contracts become vulnerable to exploitation, as all their `pub` functions would become accessible to anybody with any kind of reference to a contract or resource. From 5ad5053cac1a97d002434369fe3059b9b5b9cb40 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 14 Mar 2023 18:35:50 -0400 Subject: [PATCH 21/30] fix minor mistake --- cadence/2022-12-14-auth-remodel.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index a015178f..42d9a1d2 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -505,16 +505,16 @@ entitlement X entitlement Y entitlement Z entitlement mapping M { - E -> X - E -> Y - F -> Z + X -> E + Y -> E + Z -> F } access(M) attachment A for R { - access(F | Y) fun foo() { ... } + access(E, F) fun foo() { ... } } ``` -The preimage of `F | Y` under `M` would be `(X, Y) | Z`. It is impossible to create an `auth` reference with this entitlement set, so the `base` reference +The preimage of `E, F` under `M` would be `(X | Y), Z`. It is impossible to create an `auth` reference with this entitlement set, so the `base` reference would not be typable within the body of `foo`. To avoid this problem, we will simply prevent defining functions with impossible preimages, so non-one-to-one entitlement mappings will require careful use for attachments. From 29511977bc49cb131cd3fd669ba37f08ae89fc2a Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 15 Mar 2023 11:20:11 -0400 Subject: [PATCH 22/30] fix typo --- cadence/2022-12-14-auth-remodel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 42d9a1d2..2f55f9d2 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -72,7 +72,7 @@ resource R { 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 conjection set (or an "and"); it is accessible only to an `auth` reference with all 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. So, for example, in From 0e4e857902bb7005ab41624953789e463460f33f Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 17 Mar 2023 13:41:33 -0400 Subject: [PATCH 23/30] fix formula typo --- cadence/2022-12-14-auth-remodel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 2f55f9d2..c55820a3 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -303,7 +303,7 @@ This is because we are casting from a type that is known to possess all of the l In practice `|`-separated entitlement lists never subtype `,`-separated ones except in the trivial case. This is because we are attempting to upcast (change the type without gaining any new specificity) from a reference where we only know we possess at least one of the listed entitlement to one where we know we possess all of them. This is only possible when all of the references -in both lists are equal. More specifically, `auth (U1, U2, ... ) &X <: auth (T1 | T2 | ... ) &X` whenever `∀U ∈ {U1, U2, ...}, ∀T ∈ {T1, T2, ...}, T = U`. +in both lists are equal. More specifically, `auth (U1 | U2 | ... ) &X <: auth (T1, T2, ... ) &X` whenever `∀U ∈ {U1, U2, ...}, ∀T ∈ {T1, T2, ...}, T = U`. As one can see, this is only possible when every `U` and `T` are the same entitlement, or when the two entitlement lists each only have a single equivalent element. ### Entitlement Mapping and Nested Values From 95548336d661742d0f150ef35c974c8b5c448604 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 31 Mar 2023 11:52:50 -0400 Subject: [PATCH 24/30] remove section about restrictions on which entitlements may appear on a reference --- cadence/2022-12-14-auth-remodel.md | 42 +----------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index c55820a3..a34b473b 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -3,7 +3,7 @@ status: draft flip: NNN (do not set) authors: Daniel Sainati (daniel.sainati@dapperlabs.com) sponsor: Daniel Sainati (daniel.sainati@dapperlabs.com) -updated: 2023-03-14 +updated: 2023-03-31 --- # Entitlements @@ -199,46 +199,6 @@ pub resource R { a value of type `auth(A) &R` would be able to access `foo`, because the reference has the `A` entitlement, but not `bar`, because it does not have an entitlement for `B`. However, `baz` would be accessible on this reference, since it has `pub` access. -A reference type's entitlements must be valid entitlements of the referenced type: it is nonsensical, given a set of definitions like: - -```cadence -pub entitlement A -pub entitlement B -pub resource R { - access(A) fun foo() { ... } -} -``` - -to create a reference like like `auth(B) &R`, since `R` has no functions with `B` entitlements. Thus - -```cadence -let r <- create R() -let ref = &r as auth(B) &R // cannot take a reference to `r` with a `B` entitlement -``` - -would fail statically. It is important to note, however, that because the type on the right-hand side of the `&` may be an interface, the dynamic type of a reference may -permit more entitlements than the static type. Consider the following code: - -```cadence -pub entitlement A -pub entitlement B -pub resource interface I { - access(A) fun foo() -} -pub resource R: I { - access(A) fun foo() { ... } - access(B) fun bar() { ... } -} -let r <- create R() -let ref1 = &r as auth(A, B) &R // valid -let ref2 = ref as auth(A, B) &{I} // invalid cast -let ref3 = ref as auth(A) &{I} // valid cast -``` - -Here, `auth(A, B) &R` is a valid type because both `A` and `B` are valid entitlements for `R`. However, if we wished to upcast this to a reference of -type `&{I}`, this would not permit a `B` entitlement, so in order to upcast this we would need to drop the `B` entitlement to get `auth(A) &{I}`. The `B` -entitlement would remain present on the dynamic (run-time) type of this reference, however, and could be recovered with a runtime downcasting operation (described below). - The other part of this change is to remove the limitations on resource downcasting that used to exist. Prior to this change, non-`auth` references could not be downcast at all, since the sole purpose of the `auth` keyword was to indicate that references could be downcast. With the proposed change, all reference types can be downcast or upcast the same way any other type would be. So, for example From a362d9cb3ff5ce1f9c6417cf2bc4504091173e89 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 25 Apr 2023 12:03:13 -0400 Subject: [PATCH 25/30] rewrite attachments section to use new proposed idea --- cadence/2022-12-14-auth-remodel.md | 82 +++++++----------------------- 1 file changed, 18 insertions(+), 64 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index a34b473b..2922a0ea 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -416,17 +416,15 @@ owned values are considered fully entitled, accessing `A` directly off of an `@R is authorized for the entire image of `M`. Within the declaration of the attachment itself, the members of the attachments can use any entitlements that exist in the image of `M`, but cannot use other entitlements as the -attachment access semantics would make it impossible to obtain a reference to the attachment with entitlements not in the image of `M`. Additionally, there are new semantics for inferring -the type of `self` and `base` in the bodies of attachment member functions. +attachment access semantics would make it impossible to obtain a reference to the attachment with entitlements not in the image of `M`. -The `self` value, which currently is simply a `&A` reference for an attachment `attachment A for R` in any of `A`'s member functions, would now have its entitlements -depend on the entitlements of the member function in question. In a member declaration `access(X | Y) fun foo()` in `A`, the `self` variable would have -type `auth(X | Y) &R`, while in a member declaration in `A` `pub fun bar()`, `self` would just be an `&R`. +Within `A`'s member functions, the `self` value is an `&A` reference that is fully entitled to the image of `M`. This results in similar behavior to regular composite declarations, +where `self` is fully entitled because it is an owned value. -Meanwhile, the `base` value would have slightly more complex semantics - specifically, on an attachment `access(M) attachment A for R`, -within a member function declared with some entitlement set `S`, within that -member function the `base` reference would be typed with `auth(P) &R`, where `P` is the preimage of `S` under the mapping `M`. -This can be understood more easily when we take a concrete example; given the following declarations: +Meanwhile, the `base` value would have slightly more complex semantics. In the default case, the `base` value is an unentitled reference, which will prevent +attachment authors from using the `base` to access restricted functionality on the base resource. However, if the author of an attachment needs a specific entitlement on the `base` +in order to implement their functionality, they can explicitly require it in the declaration of the attachment using a new `require entitlement X` syntax. This will require that +the attachment explicitly be given an entitlement to `X` on creation, and thus makes `X` an available entitlement on the `base` in the declaration. So in the following example: ```cadence entitlement E @@ -438,45 +436,17 @@ entitlement mapping M { X -> Y } access(M) attachment A for R { - access(F) fun foo() { ... } - access(Y) fun bar() { ... } - access(F, Y) fun baz() { ... } - access(F | Y) fun qux() { ... } + require entitlement X + // ... } ``` -As explained previously, within `foo` the `self` reference has type `auth(F) &A`, since `foo` is only callable when one possesses at least an `F`-entitled -reference to `A`. However, when considered carefully it also becomes apparent that the given the specified mapping in `M`, the only way to obtain such a reference -is via access on an `E`-entitled reference to `R`. Specifically, an `F`-entitled reference to `A` can only be accessed on a `base` that possesses an entitlement for `E`. -As such, we can thus infer that within `foo` the `base` reference has an `auth(E) &R` type. The same logic can be used to infer that within `bar`, `base` must have an -`auth(X) &R` type. Just as we called the output of `M` its "image", the output of the "inverted" version of `M` is called its "preimage". +`base` will be considered to possess an `X` entitlement within all the members of `A`. -For more complex access modifiers, instead of computing `M`'s preimage for a single element, we compute the preimage for the entire set. I.e., within `baz` -the `base` reference would need to have type `auth(E, X) &R` in order to make `baz` callable on an `A` reference, and within `qux` it would need a -`auth(E | X) &R` type in order to make its usage safe. Specifically in the second case, because `qux` is callable with either an `F` or a `Y`-entitled reference to `A`, -we can only deduce that `base` had either an `E` or an `X` entitled reference to `R`. - -As before with regular nested objects, certain non-one-to-one mappings make inferring the `base` type impossible. In the following example: - -```cadence -entitlement E -entitlement F -entitlement X -entitlement Y -entitlement Z -entitlement mapping M { - X -> E - Y -> E - Z -> F -} -access(M) attachment A for R { - access(E, F) fun foo() { ... } -} -``` - -The preimage of `E, F` under `M` would be `(X | Y), Z`. It is impossible to create an `auth` reference with this entitlement set, so the `base` reference -would not be typable within the body of `foo`. To avoid this problem, we will simply prevent defining functions with impossible preimages, so non-one-to-one entitlement -mappings will require careful use for attachments. +Creating attachments that require certain entitlements can be done with an extension to the original `attach` expression: `attach A() to v with X, Y, ...`. Here the +`A()` is an attachment constructor call as before, while `v` is the value to which the attachment is to be attached. However, after this the user may optionally +provide a `with` followed by a list of entitlements which they wish to give to `A`. This list must superset the list of entitlements required by `A`'s declaration, +or the attach expression will fail to typecheck. This way the user must be explicit about what permissions they are giving to each attachment they create. Putting all of these rules together, we can see that given the following declaration: ```cadence @@ -492,6 +462,8 @@ interface Provider { } access(ConverterMap) attachment CurrencyConverter for Provider { + require entitlement Withdraw + pub fun convert(_ amount: UFix64): UFix64 { // ... } @@ -503,8 +475,7 @@ access(ConverterMap) attachment CurrencyConverter for Provider { access(ConvertAndWithdraw) fun withdraw(_ amount: UFix64): @Vault { let convertedAmount = self.convert(amount) - // this is permitted because this function has an entitlement to `ConvertAndWithdraw` - // on the attachment, and thus `base` has type `auth(Withdraw) &{Provider}` + // this is permitted because the attachment declaration explicitly requires an entitlement to `Withdraw` return <-base.withdraw(amount: amount) } @@ -517,28 +488,11 @@ access(ConverterMap) attachment CurrencyConverter for Provider { baseReceiver.deposit(from: <-convertedVault) } } - - pub fun maliciousStealingFunctionA(_ amount: UFix64): @Vault { - // This fails statically, as `self` here is just an `&A` - // because `maliciousStealingFunctionA`'s access is `pub`, - // and therefore `self` does not have access to `withdraw` - return <-self.withdraw(amount) - } - - pub fun maliciousStealingFunctionB(_ amount: UFix64): @Vault { - // This fails statically, as `base` here is just an `&{Provider}` - // because `maliciousStealingFunctionB`'s access is `pub`, - // and therefore `base` does not have access to `withdraw` - return <-base.withdraw(amount: amount) - } } -let vault <- attach CurrencyConverter() to <-create Vault(balance: /*...*/) +let vault <- attach CurrencyConverter() to <-create Vault(balance: /*...*/) with Withdraw let authVaultReference = &vault as auth(Withdraw) &Vault let converterRef = authVaultReference[CurrencyConverter]! // has type auth(ConvertAndWithdraw) &CurrencyConverter, can call `withdraw` - -let otherVaultReference = &vault as &Vault -let otheConverterRef = otherVaultReference[CurrencyConverter]! // has type &CurrencyConverter, cannot call `withdraw` ``` ### Drawbacks From c5d0eaeb630efd9e2e94cbe618c5093ca7c0a05f Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 25 Apr 2023 14:59:35 -0400 Subject: [PATCH 26/30] remove dynamic behavior for entitlements --- cadence/2022-12-14-auth-remodel.md | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 2922a0ea..031afb10 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -204,27 +204,16 @@ non-`auth` references could not be downcast at all, since the sole purpose of th downcast. With the proposed change, all reference types can be downcast or upcast the same way any other type would be. So, for example `&{I} as! &R` would be valid, as would `&AnyResource as? &{I}` or `&{I} as? &{J}`, using the hierarchy defined above (for any `J`). -However, the `auth`-ness (and the reference's set of entitlements) would not change on downcasting, nor would that set -be expandable via casting. The subtyping rules for `auth` references is that `auth (U1, U2, ... ) &X <: auth (T1, T2, ... ) &X` whenever `{U1, U2, ...}` -is a superset of `{T1, T2, ...}`, or equivalently `∀T ∈ {T1, T2, ...}, ∃U ∈ {U1, U2, ...}, T = U`. Of course, all `auth` reference types -would remain subtypes of all non-`auth` reference types as before. +However, the `auth`-ness (and the reference's set of entitlements) would not change on downcasting, nor would that set be expandable via casting. +In fact, the set of entitlements to which a reference is authorized is purely a static construct, and entitlements do not exist as a concept at runtime. +In particular, what this means that it is not ever possible to downcast a reference to a type with a more restrictive set of entitlements. As such, while `auth(A, B) &R` would be statically upcastable to `auth(A) &R`, since this decreases the permissions on the -reference, it would require a runtime cast to go from `auth(A) &R` to `auth(A, B) &R`, as this cast would only succeed if the -runtime type of the reference was entitled to both `A` and `B`. So in the code below: - -```cadence -fun foo(ref: &R): Bool { - let authRef = ref as? auth(A, B) &R - return authRef != nil -} -let r <- create R() -let ref1 = &r as auth(A) &R -let ref2 = &r as auth(A, B, C) &R -``` +reference, it would not be possible to go from `auth(A) &R` to `auth(A, B) &R`. -`foo` would return `true` when called with `ref2`, because the runtime type of `ref` in the failable cast is a subtype of `auth(A, B) &R`, since -`{A, B, C}` is a superset of `{A, B}`, but would return `false` when called with `ref1`, since `{A}` is not a superset of `{A, B}`. +The subtyping rules for `auth` references is that `auth (U1, U2, ... ) &X <: auth (T1, T2, ... ) &X` whenever `{U1, U2, ...}` +is a superset of `{T1, T2, ...}`, or equivalently `∀T ∈ {T1, T2, ...}, ∃U ∈ {U1, U2, ...}, T = U`. Of course, all `auth` reference types +would remain subtypes of all non-`auth` reference types as before. In addition to the `,`-separated list of entitlements (which defines a conjunction/"and" set for `auth` modifiers similarly to its behavior for `access` modifiers), it is also possible, although very rarely necessary, to define `|`-separated entitlement lists in `auth` modifers for references, like so: `auth(E1 | E2 | ...) &T`. In this case, the type denotes that the @@ -247,15 +236,12 @@ fun test(ref: auth(E | F) &R) { ref.bar() // not allowed because the reference may not have `E` and `F` ref.baz() // not allowed because the reference may not have `E` ref.qux() // not allowed because the reference may not have `F` - (ref as? auth(E) &R)?.baz() // allowed statically, will succeed at runtime if `ref` has an `E` entitlement - (ref as? auth(F) &R)?.baz() // allowed statically, will succeed at runtime if `ref` has an `F` entitlement - (ref as? auth(E, F) &R)?.qux() // allowed statically, will succeed at runtime if `ref` has both an `E` and an `F` entitlement } ``` The subtyping rules for `|`-separated entitlement lists allow lists to expand on supertyping. I.e., `auth(A | B) &R <: auth(A | B | C) &R`, as this decreases the information we have about the reference's entitlements and thus permits fewer operations. In general, `auth (U1 | U2 | ... ) &X <: auth (T1 | T2 | ... ) &X` whenever `{U1, U2, ...}` -is a subset of `{T1, T2, ...}`, or equivalently `∀U ∈ {U1, U2, ...}, ∃T ∈ {T1, T2, ...}, T = U`. To shrink the `|`-separated entitlement list, downcasting is required (as shown in the example above). +is a subset of `{T1, T2, ...}`, or equivalently `∀U ∈ {U1, U2, ...}, ∃T ∈ {T1, T2, ...}, T = U`. `,`-separated entitlement lists subtype `|`-separated ones as long as the two sets are not disjoint; that is, as long as there is an entitlement in the subtype set that is also in the supertype set. This is because we are casting from a type that is known to possess all of the listed entitlements to a type that is only guaranted to possess one. More specifically, From cf4b8d611564daedc25eb9eea05d3291136757fc Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 2 May 2023 10:39:31 -0400 Subject: [PATCH 27/30] respond to review --- cadence/2022-12-14-auth-remodel.md | 90 ++++++++++++++++++------------ 1 file changed, 54 insertions(+), 36 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 031afb10..86ad2876 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -3,7 +3,7 @@ status: draft flip: NNN (do not set) authors: Daniel Sainati (daniel.sainati@dapperlabs.com) sponsor: Daniel Sainati (daniel.sainati@dapperlabs.com) -updated: 2023-03-31 +updated: 2023-05-02 --- # Entitlements @@ -74,6 +74,8 @@ A single member definition can include multiple entitlements, using either a `|` 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. +Note that these operators cannot be mixed within a single list; any entitlement access modifier may use either `|` or `,`, but not both. + So, for example, in ```cadence @@ -90,7 +92,18 @@ resource R { Like `access(contract)` and `access(account)`, this new modifier sits exactly between `pub` and `priv` (or equivalently `access(self)`) in permissiveness; it allows less access than `pub`, but strictly more than `priv`, as an `access(X)` field or function can be used anywhere in the implementation of the composite. To see why, consider that `self` is necessarily of the same type as the composite, meaning -that the access rules defined above allow any `access(I)` members to be accessed on it. +that the access rules defined above allow any `access(I)` members to be accessed on it. A table summarizing the new access modifiers is included here: + + +| Modifier(s) | Visibility | +|-------------------------|-----------------------------------------------------------------------| +| `access(self)` (`priv`) | Methods defined within the same type object. | +| `access(contract)` | Methods defined within the same smart contract object. | +| `access(account)` | Methods defined in a contract deployed to the same account. | +| `access(X)` | Code holding an `auth(X)` reference for some defined entitlement `X`. | +| `access(all)` (`pub`) | Any code with any reference to the object. | + +As specified above, note that actual ownership of an object grants access to any `access(X)` member for any `X`. As such, the following would be prohibited statically: @@ -254,7 +267,7 @@ As one can see, this is only possible when every `U` and `T` are the same entitl ### Entitlement Mapping and Nested Values -When objects have reference fields to child objects, it can often be valuable to have different views of that reference depending on the entitlements one has on the reference to the parent object. +When objects have fields that are child objects, it can often be valuable to have different views of that reference depending on the entitlements one has on the reference to the parent object. Consider the following example: ```cadence @@ -262,28 +275,32 @@ entitlement OuterEntitlement entitlement SubEntitlement resource SubResource { - pub fun foo() { ... } + access(all) fun foo() { ... } access(SubEntitlement) fun bar() { ... } } resource OuterResource { - pub let pubRef: &SubResource - access(OuterEntitlement) let entitledRef: auth(SubEntitlement) &SubResource + access(self) let childResource: @SubResource + access(all) fun getPubRef(): &SubResource { + return &self.childResource as &SubResource + } + access(OuterEntitlement) fun getEntitledRef(): auth(SubEntitlement) &SubResource { + return &self.childResource as auth(SubEntitlement) &SubResource + } - init(ref: auth(SubEntitlement) &SubResource) { - self.pubRef = ref // `ref` is implicitly upcast here to `&SubResource` - self.entitledRef = ref + init(r: @SubResource) { + self.childResource <- r } } ``` -With this pattern, we can store a reference to a `SubResource` on an `OuterResource` value, and create different ways to access that nested resource depending on the entitlement one -posseses. Somoneone with only an unauthorized reference to `OuterResource` can only access the `pubRef` field, and thus can only get an unauthorized reference to `SubResource` that lets them call `foo`. -However, someone with a `OuterEntitlement`-authorized refererence to the `OuterResource` can access the `entitledRef` field, giving them a `SubEntitlement`-authorized reference to `SubResource` that +With this pattern, we can store to a `SubResource` on an `OuterResource` value, and create different ways to access that nested resource depending on the entitlement oneposseses. +Somoneone with only an unauthorized reference to `OuterResource` can only call the `getPubRef` function, and thus can only get an unauthorized reference to `SubResource` that lets them call `foo`. +However, someone with a `OuterEntitlement`-authorized refererence to the `OuterResource` can call the `getEntitledRef` function, giving them a `SubEntitlement`-authorized reference to `SubResource` that allows them to call `bar`. -This pattern is functional, but it is unfortunate that we are forced to "duplicate" the reference to `SubResource`, storing it twice on the object in differently named fields, essentially creating -two different views to the same object that are stored as different fields. To avoid necessitating this duplication, we add support to the language for "entitlement mappings", a way to declare +This pattern is functional, but it is unfortunate that we are forced to "duplicate" the accessors to `SubResource`, duplicating the code and storing two functions on the object, essentially creating +two different views to the same object that are stored as different functions. To avoid necessitating this duplication, we add support to the language for "entitlement mappings", a way to declare statically how entitlements are propagated from parents to child objects in a nesting hierarchy. So, the above example could be equivalently written as: ```cadence @@ -297,28 +314,29 @@ entitlement mapping Map { } resource SubResource { - pub fun foo() { ... } + access(all) fun foo() { ... } access(SubEntitlement) fun bar() { ... } } resource OuterResource { - // by referering to `Map` here, we declare that the entitlements we receive when accessing the `ref` field on this resource + access(self) let childResource: @SubResource + // by referering to `Map` here, we declare that the entitlements we receive when accessing the `getRef` function on this resource // will depend on the entitlements we possess to the resource during the access. - access(Map) let ref: auth(Map) &SubResource + access(Map) fun getRef(): auth(Map) &SubResource { + return &self.childResource as auth(Map) &SubResource + } - init(ref: auth(SubEntitlement) &SubResource) { - // because the `self.ref` field here is typed with a mapping, in order to assign to it the rhs of the assignment must be fully-entitled - // for the range of this mapping, that is, it must possess all of the entitlements in the image of `Map` - self.ref = ref + init(r: @SubResource) { + self.childResource = r } } // given some value `r` of type `@OuterResource` let pubRef = &r as &OuterResource -let pubSubRef = r.ref // has type `&SubResource` +let pubSubRef = r.getRef() // has type `&SubResource` 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` +let entiteldSubRef = r.getRef() // `OuterEntitlement` is defined to map to `SubEntitlement`, so this access yields a value of type `auth(SubEntitlement) &SubResource` ``` Entitlement mappings do not need to be 1:1; it is perfectly valid to define a mapping like so: @@ -450,11 +468,11 @@ interface Provider { access(ConverterMap) attachment CurrencyConverter for Provider { require entitlement Withdraw - pub fun convert(_ amount: UFix64): UFix64 { + access(all) fun convert(_ amount: UFix64): UFix64 { // ... } - pub fun convertVault(_ vault: @Vault): @Vault { + access(all) fun convertVault(_ vault: @Vault): @Vault { vault.balance = self.convert(vault.balance) return <-vault } @@ -465,12 +483,12 @@ access(ConverterMap) attachment CurrencyConverter for Provider { return <-base.withdraw(amount: amount) } - pub fun deposit (from: @Vault) { + access(all) fun deposit (from: @Vault) { // cast is permissable under the new reference casting rules let baseReceiverOptional = base as? &{Receiver} if let baseReceiver = baseReceiverOptional { let convertedVault <- self.convertVault(<-from) - // this is ok because `deposit` has `pub` access + // this is ok because `deposit` has `all` access baseReceiver.deposit(from: <-convertedVault) } } @@ -497,32 +515,32 @@ entitlement sets to control access. With this new design, the `Vault` heirarchy might be written like so: ```cadence -pub entitlement Withdraw +access(all) entitlement Withdraw -pub resource interface Provider { +access(all) resource interface Provider { access(Withdraw) fun withdraw(amount: UFix64): @Vault { // ... } } -pub resource interface Receiver { - pub fun deposit(from: @Vault) { +access(all) resource interface Receiver { + access(all) fun deposit(from: @Vault) { // ... } } -pub resource interface Balance { - pub var balance: UFix64 +access(all) resource interface Balance { + access(all) var balance: UFix64 } -pub resource Vault: Provider, Receiver, Balance { +access(all) resource Vault: Provider, Receiver, Balance { access(Withdraw) fun withdraw(amount: UFix64): @Vault { // ... } - pub fun deposit(from: @Vault) { + access(all) fun deposit(from: @Vault) { // ... } - pub var balance: UFix64 + access(all) var balance: UFix64 } ``` From 3ae431880c6ed56a4d2057c366219c5596ae00a3 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 10 May 2023 16:10:45 -0400 Subject: [PATCH 28/30] respond to review --- cadence/2022-12-14-auth-remodel.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 86ad2876..604a1f2a 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -3,7 +3,7 @@ status: draft flip: NNN (do not set) authors: Daniel Sainati (daniel.sainati@dapperlabs.com) sponsor: Daniel Sainati (daniel.sainati@dapperlabs.com) -updated: 2023-05-02 +updated: 2023-05-10 --- # Entitlements @@ -57,16 +57,17 @@ entitlement E In the future, this is easily extensible to allow creating entitlements that are constructed from others via operators like `&` or `|`. -### Entitlement-access fields +### Entitlement-access members To go with these `entitlement` declarations, this FLIP proposes to add a new access control modifier to field and function declarations in composite types: -`access(X)`, which allows access to either the immediate owner of the resource (i.e. anybody who has the actual resource value), -or someone with an `auth(X)` reference to the type on which the member is defined. The `X` here can be the qualified name of any entitlement, e.g.: +`access(E)`, which allows access to either the immediate owner of the resource (i.e. anybody who has the actual resource value), +or someone with an `auth(E)` reference to the type on which the member is defined. The `X` here can be the qualified name of any entitlement, e.g.: ```cadence -entitlement E +entitlement X resource R { access(E) fun foo(a: Int) { } + access(E) let bar: String } ``` A single member definition can include multiple entitlements, using either a `|` or a `,` separator when defining the list. @@ -90,7 +91,7 @@ resource R { `foo` is only calleable on a reference to `R` that is `auth` for both `E` and `F`, while `bar` is calleable on any `auth` reference that is `auth` for either `E` or `F` (or both). Like `access(contract)` and `access(account)`, this new modifier sits exactly between `pub` and `priv` (or equivalently `access(self)`) -in permissiveness; it allows less access than `pub`, but strictly more than `priv`, as an `access(X)` field or function can be used anywhere +in permissiveness; it allows less access than `pub`, but strictly more than `priv`, as an `access(E)` field or function can be used anywhere in the implementation of the composite. To see why, consider that `self` is necessarily of the same type as the composite, meaning that the access rules defined above allow any `access(I)` members to be accessed on it. A table summarizing the new access modifiers is included here: @@ -100,10 +101,10 @@ that the access rules defined above allow any `access(I)` members to be accessed | `access(self)` (`priv`) | Methods defined within the same type object. | | `access(contract)` | Methods defined within the same smart contract object. | | `access(account)` | Methods defined in a contract deployed to the same account. | -| `access(X)` | Code holding an `auth(X)` reference for some defined entitlement `X`. | +| `access(E)` | Code holding an `auth(E)` reference for some defined entitlement `E`. | | `access(all)` (`pub`) | Any code with any reference to the object. | -As specified above, note that actual ownership of an object grants access to any `access(X)` member for any `X`. +As specified above, note that actual ownership of an object grants access to any `access(E)` member for any `E`. As such, the following would be prohibited statically: @@ -132,7 +133,7 @@ let ref: auth(E) &R = &r ref.foo() ``` -Note also that while the normal interface implementation subtyping rules would allow a composite to implement an `access(X)` interface member with a `pub` +Note also that while the normal interface implementation subtyping rules would allow a composite to implement an `access(E)` interface member with a `pub` composite member, as this is less restrictive, in order to prevent users from accidentally surrendering authority and security this way, we prevent this statically. As such, the below code would not typecheck. @@ -148,7 +149,7 @@ pub resource R: I { } ``` -If users would like to expose an access-limited function to `pub` users, they can do so by wrapping the `access(X)` function in a `pub` function. +If users would like to expose an access-limited function to `pub` users, they can do so by wrapping the `access(E)` function in a `pub` function. As with normal subtyping, this would also be statically rejected: @@ -403,7 +404,7 @@ the owner of the inner resource. #### Attachments and Entitlements Attachments would interact with entitlements and access-limited members in a nuanced but intuitive manner, using the entitlement mapping feature described above. Instead -of requiring that all attachment declarations are `pub`, they can additionally be declared with an `access(X)` modifier, where `X` is the name of an entitlement mapping. +of requiring that all attachment declarations are `pub`, they can additionally be declared with an `access(E)` modifier, where `E` is the name of an entitlement mapping. When declared with an entitlement mapping access, the attachment's entitlements are propagated from the entitlements of its `base` value according to the mapping. Attachments would remain `pub` accessible, but would permit the declaration of `access`-limited members. So, for example, given some declarations like: ```cadence From b156ed4d6e21a12379df5b88bb4008c0b2d404bf Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 18 May 2023 12:10:26 -0400 Subject: [PATCH 29/30] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bastian Müller --- cadence/2022-12-14-auth-remodel.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 604a1f2a..50e0e629 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -1,12 +1,12 @@ --- status: draft -flip: NNN (do not set) +flip: 54 authors: Daniel Sainati (daniel.sainati@dapperlabs.com) sponsor: Daniel Sainati (daniel.sainati@dapperlabs.com) updated: 2023-05-10 --- -# Entitlements +# FLIP 54: Entitlements ## Objective From f6fba22281435a07cda78f5c04c15c5cceab1e9b Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 19 May 2023 13:14:16 -0400 Subject: [PATCH 30/30] add more detail to example --- cadence/2022-12-14-auth-remodel.md | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/cadence/2022-12-14-auth-remodel.md b/cadence/2022-12-14-auth-remodel.md index 604a1f2a..268bd0d5 100644 --- a/cadence/2022-12-14-auth-remodel.md +++ b/cadence/2022-12-14-auth-remodel.md @@ -513,6 +513,41 @@ entitlement sets to control access. ### Tutorials and Examples +In prior versions of Cadence, the `Vault` heirarchy might be written like so: + +```cadence +access(all) entitlement Withdraw + +access(all) resource interface Provider { + access(all) fun withdraw(amount: UFix64): @Vault { + // ... + } +} + +access(all) resource interface Receiver { + access(all) fun deposit(from: @Vault) { + // ... + } +} + +access(all) resource interface Balance { + access(all) var balance: UFix64 +} + +access(all) resource Vault: Provider, Receiver, Balance { + access(all) fun withdraw(amount: UFix64): @Vault { + // ... + } + access(all) fun deposit(from: @Vault) { + // ... + } + access(all) var balance: UFix64 +} +``` + +Here, the access control to the `Vault` is determined by the type of reference issued to it: +someone with a `&{Provider}` reference can call `withdraw`, but someone with a `&{Receiver}` cannot. + With this new design, the `Vault` heirarchy might be written like so: ```cadence @@ -545,6 +580,8 @@ access(all) resource Vault: Provider, Receiver, Balance { } ``` +Note how the primary difference here is that `withdraw` now requires a `Withdraw` entitlement. + 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` reference, but because this reference is not entitled to `Withdraw`, they would be unable to call the `withdraw` function. However, if a user possessed a `auth(Withdraw) &{Balance}` reference,