Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Documentation for entitlements #152

Merged
merged 24 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9d25873
remove pub, priv and pub(set) documentation
dsainati1 Jun 23, 2023
850676e
fix accidentally updated unrelated files
dsainati1 Jun 23, 2023
6920d01
fix accidentally updated unrelated files
dsainati1 Jun 23, 2023
bb9dd26
start updating
dsainati1 Jun 23, 2023
57ef4df
revert non-cadence doc changes
dsainati1 Jun 23, 2023
a718d55
Merge branch 'sainati/remove-pub-priv-docs' of github.com:onflow/docs…
dsainati1 Jun 23, 2023
da7306c
add documentation for attachments
dsainati1 Jun 23, 2023
2883b91
entitlement documentation
dsainati1 Jun 23, 2023
f0e86c7
more detail for entitlement mappings
dsainati1 Jun 26, 2023
ecc876b
add notes about references
dsainati1 Jun 26, 2023
581b4e1
update best practices
dsainati1 Jun 26, 2023
2463df4
remaining auth uses
dsainati1 Jun 26, 2023
ec85538
Apply suggestions from code review
dsainati1 Jun 27, 2023
12e46f9
and clarifying details
dsainati1 Jun 27, 2023
b3a1d7e
Update docs/cadence/language/references.mdx
nialexsan Jun 27, 2023
9a36f3c
Update docs/cadence/language/references.mdx
nialexsan Jun 27, 2023
0e944c1
Apply suggestions from code review
nialexsan Jun 27, 2023
81e4a0e
Merge branch 'main' of github.com:onflow/docs into sainati/remove-pub…
dsainati1 Jul 14, 2023
21d8c0c
Merge branch 'sainati/remove-pub-priv-docs' of github.com:onflow/docs…
dsainati1 Jul 14, 2023
2ae6f19
Merge branch 'sainati/entitlement-docs' of github.com:onflow/docs int…
dsainati1 Jul 14, 2023
456220b
Apply suggestions from code review
dsainati1 Jul 18, 2023
220b5c8
add explicit warning about owned values
dsainati1 Jul 18, 2023
0ba6e66
Merge branch 'main' of github.com:onflow/docs into sainati/entitlemen…
dsainati1 Jul 18, 2023
8cb5e4c
fix comment
dsainati1 Jul 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
SupunS marked this conversation as resolved.
Show resolved Hide resolved

Entitlements are a unique feature of Cadence that provide granular access control to each member of a struct or resource.
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
Entitlements can be declared using the following syntax:

```cadence
entitlement E
entitlement F
```

creates two entitlements called `E` and `F`.
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
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
```
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved

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 */}
Copy link
Member

Choose a reason for hiding this comment

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

Should we make this a comment?

Suggested change
{/* TODO: Update once mappings can be used on regular composite fields */}
<!-- TODO: Update once mappings can be used on regular composite fields -->

Copy link
Member

Choose a reason for hiding this comment

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

This wasn't addressed/fixed


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 */}
Copy link
Member

Choose a reason for hiding this comment

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

Same here: turn into a Markdown comment, i.e. <!-- ... -->

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I tried doing this, it made the CI complain. I'm pretty sure these are comments as-is, since they don't show up here: https://developers.flow.com/next/cadence/language/access-control

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:

```
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
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