Skip to content

Commit

Permalink
Documentation for entitlements (#152)
Browse files Browse the repository at this point in the history
* remove pub, priv and pub(set) documentation

* fix accidentally updated unrelated files

* fix accidentally updated unrelated files

* start updating

* revert non-cadence doc changes

* add documentation for attachments

* entitlement documentation

* more detail for entitlement mappings

* add notes about references

* update best practices

* remaining auth uses

* Apply suggestions from code review

Co-authored-by: Joshua Hannan <[email protected]>

* and clarifying details

* Update docs/cadence/language/references.mdx

* Update docs/cadence/language/references.mdx

* Apply suggestions from code review

* Apply suggestions from code review

Co-authored-by: Supun Setunga <[email protected]>

* add explicit warning about owned values

* fix comment

---------

Co-authored-by: Joshua Hannan <[email protected]>
Co-authored-by: Alex <[email protected]>
Co-authored-by: Supun Setunga <[email protected]>
  • Loading branch information
4 people authored Jul 18, 2023
1 parent eabe317 commit 4a21588
Show file tree
Hide file tree
Showing 8 changed files with 457 additions and 78 deletions.
23 changes: 0 additions & 23 deletions docs/cadence/anti-patterns.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
218 changes: 214 additions & 4 deletions docs/cadence/language/access-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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:

Expand All @@ -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,
Expand Down Expand Up @@ -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.
Loading

1 comment on commit 4a21588

@vercel
Copy link

@vercel vercel bot commented on 4a21588 Jul 18, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.