Skip to content

Commit

Permalink
docs for intersection types
Browse files Browse the repository at this point in the history
  • Loading branch information
dsainati1 committed Jul 14, 2023
1 parent 2ae6f19 commit 189578b
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 233 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
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 @@ -228,7 +228,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
Loading

0 comments on commit 189578b

Please sign in to comment.