Skip to content

Commit

Permalink
Documentation for intersection types (#167)
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

* docs for intersection types

---------

Co-authored-by: Joshua Hannan <[email protected]>
Co-authored-by: Alex <[email protected]>
  • Loading branch information
3 people authored Jul 18, 2023
1 parent 4a21588 commit b6d1364
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 72 deletions.
43 changes: 1 addition & 42 deletions docs/cadence/anti-patterns.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,47 +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.

### Another Example

When we create a public capability for our `FungibleToken.Vault` we do not use an auth capability:

```cadence
// GOOD: Create a public capability to the Vault that only exposes
// the balance field through the Balance interface
signer.link<&FlowToken.Vault{FungibleToken.Balance}>(
/public/flowTokenBalance,
target: /storage/flowTokenVault
)
```

If we were to use an authorized type for the capability, like so:

```cadence
// BAD: Create an Authorized public capability to the Vault that only exposes
// the balance field through the Balance interface
// Authorized referenced can be downcasted to their unrestricted types, which is dangerous
signer.link<auth &FlowToken.Vault{FungibleToken.Balance}>(
/public/flowTokenBalance,
target: /storage/flowTokenVault
)
```

Then anyone in the network could take that restricted reference
that is only supposed to expose the balance field and downcast it to expose the withdraw field
and steal all our money!

```cadence
// Exploit of the auth capability to expose withdraw
let balanceRef = getAccount(account)
.getCapability<auth &FlowToken.Vault{FungibleToken.Balance}>(/public/flowTokenBalance)
.borrow()!
let fullVaultRef = balanceRef as! &FlowToken.Vault
// Withdraw the newly exposed funds
let stolenFunds <- fullVaultRef.withdraw(amount: 1000000)
```

## Events from resources may not be unique

### Problem
Expand Down Expand Up @@ -277,7 +236,7 @@ which means that anyone can create a new instance of a struct without going thro
### Solution

Any contract state-modifying operations related to the creation of structs
should be contained in restricted resources instead of the initializers of structs.
should be contained in resources instead of the initializers of structs.

### Example

Expand Down
12 changes: 4 additions & 8 deletions docs/cadence/design-patterns.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -267,15 +267,11 @@ Therefore, it is important to be explicit when getting objects or references to

### Solution

A good example of when the code should specify the type being restricted is checking the FLOW balance:
The code must borrow `&FlowToken.Vault{FungibleToken.Balance}`, in order to ensure that it gets a FLOW token balance,
A good example of when the code should specify the type is checking the FLOW balance:
The code must borrow `&FlowToken.Vault`, in order to ensure that it gets a FLOW token balance,
and not just `&{FungibleToken.Balance}`, any balance – the user could store another object
that conforms to the balance interface and return whatever value as the amount.

When the developer does not care what the concrete type is, they should explicitly indicate that
by using `&AnyResource{Receiver}` instead of `&{Receiver}`.
In the latter case, `AnyResource` is implicit, but not as clear as the former case.

## Plural names for arrays and maps are preferable

e.g. `accounts` rather than `account` for an array of accounts.
Expand Down Expand Up @@ -426,7 +422,7 @@ and emits an `InboxValueUnpublished` event that the recipient can listen for off
It is also important to note that the recipient becomes the owner of the capability object once they have claimed it,
and can thus store it or copy it anywhere they have access to.
This means providers should only publish capabilities to recipients they trust to use them properly,
or limit the type with which the capability is restricted in order to only give recipients access to the functionality
or limit the type with which the capability is authorized in order to only give recipients access to the functionality
that the provider is willing to allow them to copy.

## Capability Revocation
Expand Down Expand Up @@ -483,7 +479,7 @@ transaction {
signer.unlink(/public/exampleTokenReceiver)
}
signer.link<&ExampleToken.Vault{FungibleToken.Receiver}>(
signer.link<&ExampleToken.Vault>(
/public/exampleTokenReceiver,
target: /storage/exampleTokenVault
)
Expand Down
7 changes: 3 additions & 4 deletions docs/cadence/json-cadence-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -729,14 +729,13 @@ Initializer types are encoded a list of parameters to the initializer.

---

## Restricted Types
## Intersection Types

```json
{
"kind": "Restriction",
"kind": "Intersection",
"typeID": "<fully qualified type ID>",
"type": <type>,
"restrictions": [
"types": [
<type at index 0>,
<type at index 1>,
//...
Expand Down
2 changes: 1 addition & 1 deletion docs/cadence/language/access-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ In Flow and Cadence, there are two types of access control:
by providing references to the objects.

2. Access control within contracts and objects
using `access(all)` and `access` keywords.
using `access` keywords.

For the explanations of the following keywords, we assume that
the defining type is either a contract, where capability security
Expand Down
6 changes: 3 additions & 3 deletions docs/cadence/language/accounts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ access(all) struct AuthAccount {
pub fun forEachController(_ function: (&AccountCapabilityController): Bool)
/// Issue/create a new account capability.
pub fun issue<T: &AuthAccount{}>(): Capability<T>
pub fun issue<T: &AuthAccount>(): Capability<T>
}
}
```
Expand Down Expand Up @@ -958,7 +958,7 @@ counterRef?.count // is `42`
//
// `hasCountRef` is non-`nil`, as there is an object stored under path `/storage/counter`,
// and the stored value of type `Counter` conforms to the requested type `{HasCount}`:
// the type `Counter` implements the restricted type's restriction `HasCount`
// the type `Counter` implements the intersection type's interface `HasCount`
let hasCountRef = authAccount.borrow<&{HasCount}>(from: /storage/counter)
Expand All @@ -967,7 +967,7 @@ let hasCountRef = authAccount.borrow<&{HasCount}>(from: /storage/counter)
//
// `otherRef` is `nil`, as there is an object stored under path `/storage/counter`,
// but the stored value of type `Counter` does not conform to the requested type `{Other}`:
// the type `Counter` does not implement the restricted type's restriction `Other`
// the type `Counter` does not implement the intersection type's interface `Other`
let otherRef = authAccount.borrow<&{Other}>(from: /storage/counter)
Expand Down
2 changes: 1 addition & 1 deletion docs/cadence/language/attachments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ so this indexing returns a reference to the attachment on `v`, rather than the a
If the attachment with the given type does not exist on `v`, this expression returns `nil`.

Because the indexed value must be a subtype of the indexing attachment's base type,
the owner of a resource can restrict which attachments can be accessed on references to their resource using restricted types,
the owner of a resource can restrict which attachments can be accessed on references to their resource using interface types,
much like they would do with any other field or function. E.g.

```cadence
Expand Down
2 changes: 1 addition & 1 deletion docs/cadence/language/interfaces.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ access(all) struct AnImplementation: AnInterface {
Interfaces can be used in types: The type `{I}` is the type of all objects
that implement the interface `I`.

This is called a [restricted type](./restricted-types.md):
This is called a [intersection type](./intersection-types.md):
Only the functionality (members and functions) of the interface can be used
when accessing a value of such a type.

Expand Down
92 changes: 92 additions & 0 deletions docs/cadence/language/intersection-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
title: Intersection Types
sidebar_position: 16
---

Interface types cannot be referenced directly;
instead they must be used as part of intersection types.
An intersection type represents a value that conforms to all of the interfaces listed in the intersection.

The syntax of a intersection type is `{U1, U2, ... Un}`,
where the types `U1` to `Un` are the interfaces that the type conforms to.

The members and functions of any of the set of interfaces are available.

Intersection types are useful for writing functions that work on a variety of different inputs.
For example, by using an intersection type for a parameter's type,
the function may accept any concrete value that implements all the interfaces in that intersection.
The value is restricted to the functionality of the intersection;
if the function accidentally attempts to access other functionality,
this is prevented by the static checker.

```cadence
access(all) struct interface HasID {
access(all) let id: String
}
access(all) struct A: HasID {
access(all) let id: String
init(id: String) {
self.id = id
}
}
access(all) struct B: HasID {
access(all) let id: String
init(id: String) {
self.id = id
}
}
// Create two instances, one of type `A`, and one of type `B`.
// Both types conform to interface `HasID`, so the structs can be assigned
// to variables with type `{HasID}`: Some resource type which only allows
// access to the functionality of resource interface `HasID`
let hasID1: {HasID} = A(id: "1")
let hasID2: {HasID} = B(id: "2")
// Declare a function named `getID` which has one parameter with type `{HasID}`.
// The type `{HasID}` is a short-hand for `AnyStruct{HasID}`:
// Some structure which only allows access to the functionality of interface `HasID`.
//
access(all) fun getID(_ value: {HasID}): String {
return value.id
}
let id1 = getID(hasID1)
// `id1` is "1"
let id2 = getID(hasID2)
// `id2` is "2"
```

If more than two interfaces are present in an intersection type,
any concrete value of that type must implement both of them:

```cadence
access(all) struct interface HasMetadata {
access(all) let metadata: AnyStruct
}
access(all) struct C: HasID, HasMetadata {
access(all) let id: String
access(all) var metadata: AnyStruct
init(id: String) {
self.id = id
}
fun setMetadata(_ data: AnyStruct) {
self.metadata = data
}
}
// valid, because `C` implements both `HasID` and `HasMetadata`.
let hasID3: {HasID, HasMetadata} = C(id: "3")
// valid, because `A` implements only `HasID`.
let hasID4: {HasID, HasMetadata} = A(id: "4")
```
2 changes: 1 addition & 1 deletion docs/cadence/language/references.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ So, for example, for some struct `S`, `&S` is a subtype of `&AnyStruct`, but not

```cadence
// Create an reference to the counter,
// typed with the restricted type `&{HasCount}`,
// typed with the intersection type `&{HasCount}`,
// i.e. some resource that conforms to the `HasCount` interface
//
let countRef = &counter as &{HasCount}
Expand Down
15 changes: 7 additions & 8 deletions docs/cadence/language/run-time-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,26 +91,25 @@ Run-time types can also be constructed from type identifier strings using built-
```cadence
fun CompositeType(_ identifier: String): Type?
fun InterfaceType(_ identifier: String): Type?
fun RestrictedType(identifier: String?, restrictions: [String]): Type?
fun IntersectionType(types: [String]): Type?
```

Given a type identifier (as well as a list of identifiers for restricting interfaces
in the case of `RestrictedType`), these functions will look up nominal types and
Given a type identifier (or a list of identifiers for interfaces
in the case of `IntersectionType`), these functions will look up nominal types and
produce their run-time equivalents. If the provided identifiers do not correspond
to any types, or (in the case of `RestrictedType`) the provided combination of
to any types, or (in the case of `IntersectionType`) the provided combination of
identifiers would not type-check statically, these functions will produce `nil`.

```cadence
struct Test {}
struct Test: I {}
struct interface I {}
let type: Type = CompositeType("A.0000000000000001.Test")
// `type` is `Type<Test>`
let type2: Type = RestrictedType(
identifier: type.identifier,
let type2: Type = IntersectionType(
restrictions: ["A.0000000000000001.I"]
)
// `type2` is `Type<Test{I}>`
// `type2` is `Type<{I}>`
```

Other built-in functions will construct compound types from other run-types.
Expand Down
2 changes: 1 addition & 1 deletion docs/cadence/security-best-practices.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Do not blindly sign a transaction. The transaction could for example change depl

## Types

Use [restricted types and interfaces](./language/restricted-types.md). Always use the most specific type possible, following the principle of least privilege. Types should always be as restrictive as possible, especially for resource types.
Use [intersection types and interfaces](./language/intersection-types.md). Always use the most specific type possible, following the principle of least privilege. Types should always be as restrictive as possible, especially for resource types.

If given a less-specific type, cast to the more specific type that is expected. For example, when implementing the fungible token standard, a user may deposit any fungible token, so the implementation should cast to the expected concrete fungible token type.

Expand Down
2 changes: 1 addition & 1 deletion docs/cadence/solidity-to-cadence.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ import ExampleToken from "../../contracts/ExampleToken.cdc"
access(all) fun main(account: Address): UFix64 {
let acct = getAccount(account)
let vaultRef = acct.getCapability(ExampleToken.VaultPublicPath)
.borrow<&ExampleToken.Vault{FungibleToken.Balance}>()
.borrow<&ExampleToken.Vault>()
?? panic("Could not borrow Balance reference to the Vault")

return vaultRef.balance
Expand Down
2 changes: 1 addition & 1 deletion docs/cadence/tutorial/04-capabilities.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ The `HelloAsset` object is stored in `/storage/HelloAssetTutorial`, which only t
They want any user in the network to be able to call the `hello()` method. So they make a public capability in `/public/HelloAssetTutorial`.

To create a capability, we use the `AuthAccount.link` method to link a new capability to an object in storage.
The type contained in `<>` is the restricted reference type that the capability represents.
The type contained in `<>` is the reference type that the capability represents.
The capability says that whoever borrows a reference from this capability can only have access to the fields and methods
that are specified by the type in `<>`.
The specified type has to be a subtype of the type of the object being linked to,
Expand Down

1 comment on commit b6d1364

@vercel
Copy link

@vercel vercel bot commented on b6d1364 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.