diff --git a/docs/cadence/anti-patterns.mdx b/docs/cadence/anti-patterns.mdx index c13dd1dfe5..c25e70bbc7 100644 --- a/docs/cadence/anti-patterns.mdx +++ b/docs/cadence/anti-patterns.mdx @@ -70,29 +70,6 @@ rather than inside contract utility functions. There are some scenarios where using an `AuthAccount` object is necessary, such as a cold storage multi-sig, but those cases are extremely rare and `AuthAccount` usage should still be avoided unless absolutely necessary. -## Auth references and capabilities should be avoided - -### Problem - -[Authorized references](./language/references.mdx) allow downcasting restricted -types to their unrestricted type and should be avoided unless necessary. -The type that is being restricted could expose functionality that was not intended to be exposed. -If the `auth` keyword is used on local variables they will be references. -References are ephemeral and cannot be stored. -This prevents any reference casting to be stored under account storage. -Additionally, if the `auth` keyword is used to store a public capability, serious harm -could happen since the value could be downcasted to a type -that has functionality and values altered. - -### Example - -A commonly seen pattern in NFT smart contracts is including a public borrow function -that borrows an auth reference to an NFT (eg. [NBA Top Shot](https://github.com/dapperlabs/nba-smart-contracts/blob/95fe72b7e94f43c9eff28412ce3642b69dcd8cd5/contracts/TopShot.cdc#L889-L906)). -This allows anyone to access the stored metadata or extra fields that weren't part -of the NFT standard. While generally safe in most scenarios, not all NFTs are built the same. -Some NFTs may have privileged functions that shouldn't be exposed by this method, -so please be cautious and mindful when imitating NFT projects that use this pattern. - ### Another Example When we create a public capability for our `FungibleToken.Vault` we do not use an auth capability: diff --git a/docs/cadence/language/access-control.md b/docs/cadence/language/access-control.md index 69cbc47774..128ba582cb 100644 --- a/docs/cadence/language/access-control.md +++ b/docs/cadence/language/access-control.md @@ -34,10 +34,10 @@ and fields (in structures, and resources) are always only able to be written to and mutated (modified, such as by indexed assignment or methods like `append`) in the scope where it is defined (self). -There are four levels of access control defined in the code that specify where +There are five levels of access control defined in the code that specify where a declaration can be accessed or called. -- **Public** or **access(all)** means the declaration +- Public or **access(all)** means the declaration is accessible/visible in all scopes. This includes the current scope, inner scopes, and the outer scopes. @@ -47,7 +47,18 @@ a declaration can be accessed or called. This does not allow the declaration to be publicly writable though. An element is made publicly accessible / by any code - by using the `access(all)` or `access(all)` keywords. + by using the `access(all)` keyword. + +- Entitled access means the declaration is only accessible/visible + to the owner of the object, or to references that are authorized to the required entitlements. + + A reference is considered authorized to an entitlement if that entitlement appears in the `auth` portion of the reference type. + + For example, an `access(E, F)` field on a resource `R` can only be accessed by an owned (`@R`-typed) value, + or a reference to `R` that is authorized to the `E` and `F` entitlements (`auth(E, F) &R`). + + An element is made accessible by code in the same containing type + by using the `access(E)` syntax, described in more detail in the entitlements section below. - **access(account)** means the declaration is only accessible/visible in the scope of the entire account where it is defined. This means that @@ -84,10 +95,12 @@ To summarize the behavior for variable declarations, constant declarations, and | `let` | `access(contract)` | Current, inner, and containing contract | *None* | Current and inner | | `let` | `access(account)` | Current, inner, and other contracts in same account | *None* | Current and inner | | `let` | `access(all)` | **All** | *None* | Current and inner | +| `let` | `access(E)` | **All** with required entitlements | *None* | Current and inner | | `var` | `access(self)` | Current and inner | Current and inner | Current and inner | | `var` | `access(contract)` | Current, inner, and containing contract | Current and inner | Current and inner | | `var` | `access(account)` | Current, inner, and other contracts in same account | Current and inner | Current and inner | | `var` | `access(all)` | **All** | Current and inner | Current and inner | +| `var` | `access(E)` | **All** with required entitlements | Current and inner | Current and inner | To summarize the behavior for functions: @@ -96,7 +109,8 @@ To summarize the behavior for functions: | `access(self)` | Current and inner | | `access(contract)` | Current, inner, and containing contract | | `access(account)` | Current, inner, and other contracts in same account | -| ``access(all)` | **All** | +| `access(all)` | **All** | +| `access(E)` | **All** with required entitlements | Declarations of structures, resources, events, and [contracts](./contracts.mdx) can only be public. However, even though the declarations/types are publicly visible, @@ -205,3 +219,199 @@ some.f[3] = 1 // Valid: can call non-mutating methods on a public field in outer scope some.f.contains(0) ``` + +## Entitlements + +Entitlements are a unique feature of Cadence that provide granular access control to each member of a struct or resource. +Entitlements can be declared using the following syntax: + +```cadence +entitlement E +entitlement F +``` + +creates two entitlements called `E` and `F`. +Entitlements can be imported from other contracts and used the same way as other types. +If using entitlements defined in another contract, the same qualified name syntax is used as for other types: + +```cadence +contract C { + entitlement E +} +``` + +Outside of `C`, `E` is used with `C.E` syntax. +Entitlements exist in the same namespace as types, so if your contract defines a resource called `R`, +it will not be possible to define an entitlement that is also called `R`. + +Entitlements can be used in access modifiers on struct and resource members to specify which references to those composites +are allowed to access those members. +An access modifier can include more than one entitlement, joined with either an `|`, to indicate disjunction or "or", +or a `,`, to indicate conjunction or "and". So, for example: + +```cadence +access(all) resource SomeResource { + + // requires an `E` entitlement to read this field + access(E) let a: Int + + // requires either an `E` or an `F` entitlement to read this field + access(E | F) let b: Int + + // requires both an `E` and an `F` entitlement to read this field + access(E, F) let b: Int + + // intializers omitted for brevity + // ... +} +``` + +Given some values with the annotated types (details on how to create entitled references can be found [here](./references.mdx)): + +```cadence + +let r: @SomeResource = // ... +let refE: auth(E) &SomeResource = // ... +let refF: auth(F) &SomeResource = // ... +let refEF: auth(E, F) &SomeResource = // ... + +// valid, because `r` is owned and thus is "fully entitled" +r.a +// valid, because `r` is owned and thus is "fully entitled" +r.b +// valid, because `r` is owned and thus is "fully entitled" +r.c + +// valid, because `refE` has an `E` entitlement as required +refE.a +// valid, because `refE` has one of the two required entitlements +refE.b +// invalid, because `refE` only has one of the two required entitlements +refE.c + +// invalid, because `refF` has an `E` entitlement, not an `F` +refF.a +// valid, because `refF` has one of the two required entitlements +refF.b +// invalid, because `refF` only has one of the two required entitlements +refF.c + +// valid, because `refEF` has an `E` entitlement +refEF.a +// valid, because `refEF` has both of the two required entitlements +refEF.b +// valid, because `refEF` has both of the two required entitlements +refEF.c +``` + +Note particularly in this example how the owned value `r` can access all entitled members on `SomeResource`; +owned values are not affected by entitled declarations. + +### Entitlement Mappings + +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 +entitlement OuterEntitlement +entitlement SubEntitlement + +resource SubResource { + access(all) fun foo() { ... } + access(SubEntitlement) fun bar() { ... } +} + +resource OuterResource { + 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(r: @SubResource) { + self.childResource <- r + } +} +``` + +With this pattern, we can store 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 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 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 +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 range or the image) +entitlement mapping Map { + OuterEntitlement -> SubEntitlement +} + +resource SubResource { + access(all) fun foo() { ... } + access(SubEntitlement) fun bar() { ... } +} + +resource OuterResource { + 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) fun getRef(): auth(Map) &SubResource { + return &self.childResource as auth(Map) &SubResource + } + + init(r: @SubResource) { + self.childResource = r + } +} + +// given some value `r` of type `@OuterResource` +let pubRef = &r as &OuterResource +let pubSubRef = pubRef.getRef() // has type `&SubResource` + +let entitledRef = &r as auth(OuterEntitlement) &OuterResource +let entitledSubRef = entitledRef.getRef() // `OuterEntitlement` is defined to map to `SubEntitlement`, so this access yields a value of type `auth(SubEntitlement) &SubResource` +Entitlement + +// `r` is an owned value, and is thus considered "fully-entitled" to `OuterResource`, +// so this access yields a value authorized to the entire image of `Map`, in this case `SubEntitlement` +let alsoEntitledSubRef = r.getRef() +``` + +{/* TODO: Update once mappings can be used on regular composite fields */} + +Entitlement mappings may be used either in accessor functions (as in the example above), or in fields whose types are references. Note that this latter +usage will necessarily make the type of the composite non-storage, since it will have a reference field. + +{/* TODO: once the Account type refactor is complete and the documentation updated, let's link here to the Account type page as an example of more complex entitlement mappings */} +Entitlement mappings need not be 1:1; it is valid to define a mapping where multiple inputs map to the same output, or where one input maps to multiple outputs. + +Entitlement mappings preserve the "kind" of the set they are mapping; i.e. mapping an "and" set produces an "and" set, and mapping an "or" set produces an "or" set. +Because "and" and "or" separators cannot be combined in the same set, attempting to map "or"-separated sets through certain complex mappings may result in a type error. For example: + +``` +entitlement mapping M { + A -> B + A -> C + D -> E +} +``` + +attempting to map `(A | D)` through `M` will fail, since `A` should map to `(B, C)` and `D` should map to `E`, but these two outputs cannot be combined into a disjunctive set. \ No newline at end of file diff --git a/docs/cadence/language/attachments.mdx b/docs/cadence/language/attachments.mdx index 111e38dd95..78126e6688 100644 --- a/docs/cadence/language/attachments.mdx +++ b/docs/cadence/language/attachments.mdx @@ -14,7 +14,7 @@ without requiring the original author of the type to plan or account for the int ## Declaring Attachments Attachments are declared with the `attachment` keyword, which would be declared using a new form of composite declaration: -`access(all) attachment for : { ... }`, where the attachment functions and fields are declared in the body. +`attachment for : { ... }`, where the attachment functions and fields are declared in the body. As such, the following would be examples of legal declarations of attachments: ```cadence @@ -36,7 +36,14 @@ Note that the base type may be either a concrete composite type or an interface. In the former case, the attachment is only usable on values specifically of that base type, while in the case of an interface the attachment is usable on any type that conforms to that interface. -As with other type declarations, attachments may only have a `access(all)` access modifier (if one is present). +Unlike other type declarations, attachments may use either an `access(all)` access modifier, or an `access(M)` modifier, +where `M` is the name of an entitlement mapping. +When attachments are defined with an `access(all)` modifier, +members on the attachment may not use any entitlements in their access modifiers, +and any references to that attachment are always unauthorized. +When attachments are defined with an an [entitlement mapping](./access-control.md), +members on the attachments may use any entitlements in the range of that mapping, +and any references to that attachments will have their authorization depend on the entitlements present on the base type on which they are accessed. The body of the attachment follows the same declaration rules as composites. In particular, they may have both field and function members, @@ -46,6 +53,8 @@ and such members must be explicitly handled in the `destroy` function. The `self` keyword is available in attachment bodies, but unlike in a composite, `self` is a **reference** type, rather than a composite type: In an attachment declaration for `A`, the type of `self` would be `&A`, rather than `A` like in other composite declarations. +If the attachment declaration uses an `access(all)` access modifier, the `self` reference is always unauthorized, +whereas if it uses an `access(M)` access modifier, the `self` reference is fully-entitled to the range of `M`. If a resource with attachments on it is `destroy`ed, the `destroy` functions of all its attachments are all run in an unspecified order; `destroy` should not rely on the presence of other attachments on the base type in its implementation. @@ -111,6 +120,33 @@ access(all) attachment SomeAttachment for SomeContract.SomeStruct { } ``` +By default, the `base` reference is unauthorized, and thus entitled-access members on the base type are inaccessible in the attachment. +If the author of the attachment wishes to have access to entitled-access members on the base type, +they must declare that their attachment requires certain entitlements to the base, using `require entitlement E` syntax. +Required entitlements must be valid entitlements for the base type, +and requiring entitlements in the attachment declaration will impose additional requirements when the attachment is attached, +as described below. So, for example: + +```cadence +entitlement mapping M { + E -> F +} + +resource R { + access(E) fun foo() { + //... + } +} + +access(M) attachment A for R { + require entitlement E + + access(all) fun bar() { + base.foo() // available because `E` is required above, and thus `base` is type `auth(E) &R`. + } +} +``` + ### Attachment Types An attachment declared with `access(all) attachment A for C { ... }` will have a nominal type `A`. @@ -169,6 +205,14 @@ Cadence will raise a runtime error if a user attempts to add an attachment to a The type returned by the `attach` expression is the same type as the expression on the right-hand side of the `to`; attaching an attachment to a value does not change its type. +If an attachment has required entitlements to its base, those entitlements must be explicitly provided in the `attach` expression +using an additional `with` syntax. So, for example, if an attachment `A` declared for `R` requires entitlements `E` and `F`, it can +be attached to an `r` of type `@R` like so: + +```cadence +let rWithA <- attach A() to <-r with (E, F) +``` + ## Accessing Attachments Attachments are accessed on composites via type-indexing: @@ -201,6 +245,31 @@ fun foo(r: &{I}) { Hence, if the owner of a resource wishes to allow others to use a subset of its attachments, they can create a capability to that resource with a borrow type that only allows certain attachments to be accessed. +If an attachment is declared with an `access(all)` modifier, +accessing one this way will always produce an unauthorized reference to the attachment. +However, if the attachment is declared with an `access(M)` modifier, where `M` is some entitlement mapping, +then the authorization of the resulting reference will depend on the authorization of the accessed value. + +So, for example, given a declaration + +```cadence +entitlement E +entitlement F +entitlement G +entitlement H +entitlement mapping M { + E -> F + G -> H +} +resource R {} +access(M) attachment A for R {} +``` + +when `A` is accessed on an owned value of type `@R`, it will be fully-authorized to the domain of `M`, +having a type of `auth(F, H) &A`. +However, if `A` is accessed on an `auth(E) &R` reference, then it will only have a `auth(F) &A` type. +If `A` is accessed on an unauthorized `&R` reference, then it will yield an unauthorized `&A` type. + ## Removing Attachments Attachments can be removed from a value with a `remove` statement. diff --git a/docs/cadence/language/capability-based-access-control.md b/docs/cadence/language/capability-based-access-control.md index dbba404c80..914b81bdee 100644 --- a/docs/cadence/language/capability-based-access-control.md +++ b/docs/cadence/language/capability-based-access-control.md @@ -140,10 +140,14 @@ This can be done using the `borrow` function of the capability: execution will abort with an error. ```cadence +entitlement Reset + // Declare a resource interface named `HasCount`, that has a field `count` +// and a `resetCount` function requiring a `Reset` entitlement // resource interface HasCount { count: Int + access(Reset) fun resetCount() } // Declare a resource named `Counter` that conforms to `HasCount` @@ -158,6 +162,10 @@ resource Counter: HasCount { access(all) fun increment(by amount: Int) { self.count = self.count + amount } + + access(Reset) fun resetCount() { + self.count = 0 + } } // In this example an authorized account is available through the constant `authAccount`. @@ -221,22 +229,19 @@ countRef.count // is `42` // countRef.increment(by: 5) -// Again, attempt to get a get a capability for the counter, but use the type `&Counter`. +// Again, attempt to get a get a capability for the counter, but use the type `auth(Reset) &{HasCount}`. // // Getting the capability succeeds, because it is latent, but borrowing fails // (the result s `nil`), because the capability was created/linked using the type `&{HasCount}`: // -// The resource type `Counter` implements the resource interface `HasCount`, -// so `Counter` is a subtype of `{HasCount}`, but the capability only allows -// borrowing using unauthorized references of `{HasCount}` (`&{HasCount}`) -// instead of authorized references (`auth &{HasCount}`), -// so users of the capability are not allowed to borrow using subtypes, -// and they can't escalate the type by casting the reference either. +// Because the stored capability is not authorized to the `Reset` entitlement, it cannot +// be borrowed with that type, and thus only the functions on `HasCount` that do not require +// an entitlement are available to this capability. // // This shows how parts of the functionality of stored objects // can be safely exposed to other code // -let countCapNew = publicAccount.getCapability<&Counter>(/public/hasCount) +let countCapNew = publicAccount.getCapability(/public/hasCount) let counterRefNew = countCapNew.borrow() // `counterRefNew` is `nil`, the borrow failed diff --git a/docs/cadence/language/glossary.mdx b/docs/cadence/language/glossary.mdx index e278db922e..ac37936b39 100644 --- a/docs/cadence/language/glossary.mdx +++ b/docs/cadence/language/glossary.mdx @@ -23,12 +23,12 @@ let refOfA: &String = &a as &String References may also be authorized if the `&` symbol is preceded by `auth` (otherwise the reference is unauthorized). -Authorized references have the `auth` modifier, i.e. the full syntax is `auth &T`, -whereas unauthorized references do not have a modifier. +Authorized references have the `auth` modifier, along with the set of entitlements to which the reference is authorized, +i.e. the full syntax is `auth(E, F) &T`, whereas unauthorized references do not have a modifier. ```cadence let a: String = "hello" -let refOfA: &String = &a as auth &String +let refOfA: auth(X) &String = &a as auth(X) &String ``` ### Logical Operator diff --git a/docs/cadence/language/references.mdx b/docs/cadence/language/references.mdx index 454cfbbb98..15cdb1d88c 100644 --- a/docs/cadence/language/references.mdx +++ b/docs/cadence/language/references.mdx @@ -99,18 +99,11 @@ counterRef.increment() counterRef.count // is `43` ``` -References may be **authorized** or **unauthorized**. - -Authorized references have the `auth` modifier, i.e. the full syntax is `auth &T`, -whereas unauthorized references do not have a modifier. - -Authorized references can be freely upcasted and downcasted, -whereas unauthorized references can only be upcasted. -Also, authorized references are subtypes of unauthorized references. +References can be freely upcasted and downcasted, and are covariant in their referenced type. +So, for example, for some struct `S`, `&S` is a subtype of `&AnyStruct`, but not of `&Int`. ```cadence - -// Create an unauthorized reference to the counter, +// Create an reference to the counter, // typed with the restricted type `&{HasCount}`, // i.e. some resource that conforms to the `HasCount` interface // @@ -123,37 +116,163 @@ countRef.count // is `43` // countRef.increment() -// Invalid: Cannot conditionally downcast to reference type `&Counter`, -// as the reference `countRef` is unauthorized. -// -// The counter value has type `Counter`, which is a subtype of `{HasCount}`, -// but as the reference is unauthorized, the cast is not allowed. -// It is not possible to "look under the covers" +// We can conditionally downcast `countRef` to a `Counter` if it has +// that type at runtime. // let counterRef2: &Counter = countRef as? &Counter +counterRef2.increment() +``` -// Create an authorized reference to the counter, -// again with the restricted type `{HasCount}`, i.e. some resource -// that conforms to the `HasCount` interface -// -let authCountRef = &counter as auth &{HasCount} +References are ephemeral, i.e. they cannot be [stored](accounts#account-storage). +Instead, consider [storing a capability and borrowing it](capability-based-access-control) when needed. -// Conditionally downcast to reference type `&Counter`. -// This is valid, because the reference `authCountRef` is authorized -// -let counterRef3: &Counter = authCountRef as? &Counter +## Authorized References + +By default, references are **unauthorized**. +However, they may also be **authorized** to a set of [entitlements](./access-control.md#entitlements) + +Authorized references have the `auth` modifier, +along with the set of entitlements to which they are authorized. The full syntax is: +`auth(E, F, G) &T` for a reference authorized to `E`, `F` and `G`, +or `auth(E | F | G) &T` for a refernece authorized to `E`, `F`, **or** `G`. +Authorized references are subtypes of unauthorized references. + +Entitlements can only be given to references when they are created, +and references to a value can only be created by the owner of the value. +When creating a reference, that reference can be given any set of entitlements the value owner wishes to add. -counterRef3.count // is `43` +Possessing an entitlement allows a reference to have access to functions and fields on its referenced type +that require that entitlement. E.g, if we extended the `HasCount` interface with a function: -counterRef3.increment() +```cadence +entitlement Reset -counterRef3.count // is `44` +resource interface HasCount { + count: Int + access(Reset) fun resetCount() +} ``` -References are ephemeral, i.e. they cannot be [stored](accounts#account-storage). -Instead, consider [storing a capability and borrowing it](capability-based-access-control) when needed. +Then an unauthorized reference of type `&{HasCount}` would be unable to call `resetCount`. +However, we can create a reference that can, like so: + + +``` +let authCountRef: auth(Reset) &{HasCount} = &counter + +// Valid, because `authCountRef` is authorized to `Reset` +authCountRef.resetCount() +``` + +It is important to note that while references are covariant (and downcastable) with respect to their reference type, +the authorization portion of the reference can never be downcast. +In fact, the only way to "add" entitlements to a reference is to do so at the time of its creation, like in the example above. +A reference will never have any more entitlements than the set with which it was created, +and the set of entitlements on a reference at runtime will always match the set expressed in its static type. +One implication of this is that upcasting an authorized reference actually changes its runtime type: + +``` +let authCountRef: auth(Reset) &{HasCount} = &counter +let unauthCountRef = authCountRef as &{HasCount} +let authCountRef2 = unauthCountRef as? auth(Reset) &{HasCount} + +// Invalid: `authCountRef2` is `nil`, as the upcast of `authCountRef` cleared the +// `Reset` entitlement from the reference, meaning that it cannot be regained on downcasting. +authCountRef2.resetCount() +``` + +The benefit of this is that there is never any "surprising" behavior with regards to entitlements, +every reference value is transparent about what it is capable of at runtime. + +While entitlement sets on references cannot be downcast, they can be upcast, or used in places expecting supertypes, +and have special subtyping rules based on whether they are `|` or `,`-separated sets. + +In general, an entitlement set `{Us}` is a subtype of an entitlement set `{Vs}` when `{Us}` has more entitlements +in it than `{Vs}`, and when both are `,`-separated (as they will be in most cases), this is the rule exactly: +`{Us}` is a subset of `{Vs}` when it is a superset of `{Vs}`. + +Conversely, if both are `|`-separated, the rule is reversed: +`{Us}` is a subset of `{Vs}` when it is a subset of `{Vs}`. +It may be helpful to think of this as saying that `{Us}` is more specific than `{Vs}` in this case; +`{Vs}` expresses a set of entitlements that the reference **might** possess, +while `{Us}` is expressing a more specific set of potential entitlements. + +Lastly, if `{Us}` is `,`-separated while `{Vs}` is `|`-separated, +then `{Us}` is a subset of `{Vs}` when any of the `Us` also appears in `{Vs}`. +To see why, consider again that `{Vs}` expresses a set of entitlements that the reference **might** possess, +and as long as at least one of these entitlements is in `{Us}` (which is a set of entitlements that we **know** the reference has), +then the description provided by `{Vs}` is correct. + +As an example to illustrate these rules: + +``` +let eRef: auth(E) &T = ... +let efRef: auth(E, F) &T = ... +let eOrFRef: auth(E | F) &T = ... + +// Invalid, `eRef` only has `E` but `F` is required +eRef as auth(F) &T + +// Invalid, `eRef` only has `E` but both `E` and `F` are required +eRef as auth(E, F) &T + +// Valid, `eRef` definitely has `E` and either `E` or `F` is sufficient +eRef as auth(E | F) &T + +// Valid, `efRef` both `E` and `F` but only `F` is required +efRef as auth(F) &T + +// Valid, `efRef` both `E` and `F`, and either is sufficient +efRef as auth(E | F) &T + +// Invalid, `eOrFRef` has one of `E` or `F` but we need to definitely have `F` +eOrFRef as auth(F) &T + +// Invalid, `eOrFRef` has one of `E` or `F` but we need both +eOrFRef as auth(E, F) &T +``` + +### References and Entitlement Mappings + +In most situations, an [entitlement mapping](./access-control.md#entitlement-mappings) is valid in the `auth` portion of a reference type. +However, in certain specific circumstances in the definition of a field or function on a composite type, an entitlement mapping may be used in an `auth` modifier. + +When a field is defined with an entitlement mapping: + +```cadence +entitlement mapping M { + // omitted +} +resource interface I { + access(M) let foo: auth(M) &T +} +``` + +Here, the `M` in `auth(M) &T` indicates that the entitlements that the reference produced by an `iRef.foo` access will have +are determined by the entitlements to `I` that `iRef` has, for some `iRef` value that is a reference to `{I}`. Conceptually, +it creates a correspondence between the "output" reference's type and the "input" access modifier. + +When an accessor function is defined with an entitlement mapping: + +```cadence +entitlement mapping M { + // omitted +} +resource I { + access(self) let myField: T + + access(M) fun getMyField(): auth(M) &T { + return &self.myField as auth(M) &T + } +} +``` + +The `M` in the `auth(M) &T` of the function's return type annotation indicates the same thing as in the field case. +However, in this example `M` is also used in a reference type within the body of the function. +Inside the body of function with entitlement-mapped access, +the name of the entitlement mapping may be used as a stand-in for the output entitlements of the map. -### Reference validity +## Reference validity Ephemeral references stay valid throughout the course of the program. However, **references to resources** can become invalid during the execution of a program, diff --git a/docs/cadence/language/run-time-types.md b/docs/cadence/language/run-time-types.md index d2067d4936..2b25eedfc9 100644 --- a/docs/cadence/language/run-time-types.md +++ b/docs/cadence/language/run-time-types.md @@ -124,7 +124,7 @@ fun FunctionType(parameters: [Type], return: Type): Type fun DictionaryType(key: Type, value: Type): Type? // returns `nil` if `type` is not a reference type fun CapabilityType(_ type: Type): Type? -fun ReferenceType(authorized: bool, type: Type): Type +fun ReferenceType(entitlements: [String], type: Type): Type? ``` ### Asserting the Type of a Value diff --git a/docs/cadence/security-best-practices.mdx b/docs/cadence/security-best-practices.mdx index 1d78f48081..319acb6961 100644 --- a/docs/cadence/security-best-practices.mdx +++ b/docs/cadence/security-best-practices.mdx @@ -11,11 +11,8 @@ Some practices listed below might overlap with advice in the [Cadence Anti-Patte [References](./language/references.mdx) are ephemeral values and cannot be stored. If persistence is required, store a capability and borrow it when needed. -Authorized references (references with the `auth` keyword) allow downcasting, e.g. a restricted type to its unrestricted type and should only be used in some specific cases. - -When exposing functionality, provide the least access necessary. Do not use authorized references, as they can be downcasted, potentially allowing a user to gain access to supposedly restricted functionality. For example, the fungible token standard provides an interface to get the balance of a vault, without exposing the withdrawal functionality. - -Be aware that the subtype or unrestricted type could expose functionality that was not intended to be exposed. Do not use authorized references when exposing functionality. For example, the fungible token standard provides an interface to get the balance of a vault, without exposing the withdrawal functionality. +When exposing functionality, provide the least access necessary. When creating an authorized reference, +create it with only the minimal set of entitlements required to achieve the desired functionality. ## Account Storage @@ -61,4 +58,6 @@ Declaring a field as [`access(all)`](./language/access-control.md) only protects Prefer non-public access to a mutable state. That state may also be nested. For example, a child may still be mutated even if its parent exposes it through a field with non-settable access. -Do not use the `access(all)` modifier on fields and functions unless necessary. Prefer `access(self)`, or `access(contract)` and `access(account)` when other types in the contract or account need to have access. +Do not use the `access(all)` modifier on fields unless necessary. +Prefer `access(self)`, or `access(contract)` and `access(account)` when other types in the contract or account need to have access, +and entitlement-based access for other cases.