Skip to content

Commit

Permalink
add switchboard entitlements and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuahannan committed Mar 29, 2024
1 parent bd13311 commit 1519d10
Show file tree
Hide file tree
Showing 26 changed files with 269 additions and 435 deletions.
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ There is no need to deploy them yourself.
| Sandboxnet | `0xe20612a0776ca4bf` |
| Mainnet | `0xf233dcee88fe0abe` |

The `Burner` contract is also deployed to these addresses, but should not be used until after the Cadence 1.0 network upgrade.
The `Burner` contract is also deployed to these addresses, but should only be used in Cadence 1.0 `FungibleToken` implementations of the standard.

## Basics of the Standard:

Expand All @@ -72,13 +72,14 @@ Specifies that the implementing type must have a `UFix64` `balance` field.
- `access(all) var balance: UFix64`

### `Provider` Interface
Defines a [`withdraw ` function](contracts/FungibleToken.cdc#L95) for withdrawing a specific amount of tokens *amount*.
Defines a [`withdraw ` function](contracts/FungibleToken.cdc#L95) for withdrawing a specific amount of tokens as *amount*.
- `access(all) fun withdraw(amount: UFix64): @{FungibleToken.Vault}`
- Conditions
- the returned Vault's balance must equal the amount withdrawn
- The amount withdrawn must be less than or equal to the balance
- The resulting balance must equal the initial balance - amount
- Users can give other accounts a reference to their `Vault` cast as a `Provider`
- Users can give other accounts a persistent Capability
or ephemeral reference to their `Vault` cast as a `Provider`
to allow them to withdraw and send tokens for them.
A contract can define any custom logic to govern the amount of tokens
that can be withdrawn at a time with a `Provider`.
Expand All @@ -89,7 +90,7 @@ Defines a [`withdraw ` function](contracts/FungibleToken.cdc#L95) for withdrawin
If the `Vault` is not in account storage when the event is emitted,
`from` will be `nil`.
- Contracts do not have to emit their own events,
the standard events will automatically be emitted.
the standard events will automatically be emitted from the interface contract with values identifying the relevant `Vault`.

Defines [an `isAvailableToWithdraw()` function](contracts/FungibleToken.cdc#L95)
to ask a `Provider` if the specified number of tokens can be withdrawn from the implementing type.
Expand All @@ -104,14 +105,14 @@ Defines functionality to depositing fungible tokens into a resource object.
- It is important that if you are making your own implementation of the fungible token interface that
you cast the input to `deposit` as the type of your token.
`let vault <- from as! @ExampleToken.Vault`
The interface specifies the argument as `@FungibleToken.Vault`, any resource that satisfies this can be sent to the deposit function. The interface checks that the concrete types match, but you'll still need to cast the `Vault` before storing it.
The interface specifies the argument as `@{FungibleToken.Vault}`, any resource that satisfies this can be sent to the deposit function. The interface checks that the concrete types match, but you'll still need to cast the `Vault` before incrementing the receiving `Vault`'s balance.
- deposit event
- [`FungibleToken.Deposited` event](contracts/FungibleToken.cdc#L53) from the standard
that indicates how much was deposited and to what account the `Vault` is stored in.
- If the `Vault` is not in account storage when the event is emitted,
`to` will be `nil`.
- This event is emitted automatically on any deposit, so projects do not need
to define and emit their own events.
- This event is emitted from the interface contract automatically on any deposit,
so projects do not need to define and emit their own events.

Defines Functionality for Getting Supported Vault Types
- Some resource types can accept multiple different vault types in their deposit functions,
Expand Down Expand Up @@ -180,8 +181,13 @@ the format to query and return them, so projects can still be flexible with how
The [FungibleTokenMetadataViews contract](contracts/FungibleTokenMetadataViews.cdc) defines four new views that can used to communicate any fungible token information:

1. `FTView`: A view that wraps the two other views that actually contain the data.
2. `FTDisplay`: The view that contains all the information that will be needed by other dApps to display the fungible token: name, symbol, description, external URL, logos and links to social media.
3. `FTVaultData`: The view that can be used by other dApps to interact programmatically with the fungible token, providing the information about the public and private paths used by default by the token, the public and private linked types for exposing capabilities and the function for creating new empty vaults. You can use this view to [setup an account using the vault stored in other account without the need of importing the actual token contract.](transactions/setup_account_from_vault_reference.cdc)
2. `FTDisplay`: The view that contains all the information that will be needed
by other dApps to display the fungible token: name, symbol, description, external URL, logos and links to social media.
3. `FTVaultData`: The view that can be used by other dApps to interact programmatically
with the fungible token, providing the information about the public and storage paths
used by default by the token, the public linked types for exposing capabilities
and the function for creating new empty vaults.
You can use this view to [setup an account using the vault stored in other account without the need of importing the actual token contract.](transactions/setup_account_from_vault_reference.cdc)
4. `TotalSupply`: Specifies the total supply of the given token.

### How to implement metadata
Expand Down Expand Up @@ -219,7 +225,8 @@ the `FungibleToken` definition to accounts yourself.
It is a pre-deployed interface in the emulator, testnet, mainnet,
and playground and you can import definition from those accounts:
- `0xee82856bf20e2aa6` on emulator
- `0x9a0766d93b6608b7` on testnet
- `0xa0225e7000ac82a9 ` on previewnet
- `0x9a0766d93b6608b7` on testnet/crescendo
- `0xf233dcee88fe0abe` on mainnet
2. Deploy the `ExampleToken` definition, making sure to import the `FungibleToken` interface.
3. You can use the `get_balance.cdc` or `get_supply.cdc` scripts to read the
Expand Down Expand Up @@ -477,7 +484,7 @@ This can be observed in the template transaction `transactions/switchboard/remov
# Running Automated Tests

There are two sets of tests in the repo, Cadence tests and Go tests.
The Cadence tests are much more straightforward nad are all written in Cadence,
The Cadence tests are much more straightforward and are all written in Cadence,
so we recommend following those.

## Cadence Testing Framework
Expand Down
24 changes: 11 additions & 13 deletions contracts/ExampleToken.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ access(all) contract ExampleToken: FungibleToken {
/// Total supply of ExampleTokens in existence
access(all) var totalSupply: UFix64

/// Admin Path
/// Storage and Public Paths
access(all) let VaultStoragePath: StoragePath
access(all) let VaultPublicPath: PublicPath
access(all) let ReceiverPublicPath: PublicPath
access(all) let AdminStoragePath: StoragePath

access(all) view fun getContractViews(resourceType: Type?): [Type] {
Expand Down Expand Up @@ -83,17 +86,9 @@ access(all) contract ExampleToken: FungibleToken {
/// The total balance of this vault
access(all) var balance: UFix64

access(all) var storagePath: StoragePath
access(all) var publicPath: PublicPath
access(all) var receiverPath: PublicPath

// initialize the balance at resource creation time
init(balance: UFix64) {
self.balance = balance
let identifier = "exampleTokenVault"
self.storagePath = StoragePath(identifier: identifier)!
self.publicPath = PublicPath(identifier: identifier)!
self.receiverPath = PublicPath(identifier: "exampleTokenReceiver")!
}

/// Called when a fungible token is burned via the `Burner.burn()` method
Expand Down Expand Up @@ -202,6 +197,9 @@ access(all) contract ExampleToken: FungibleToken {
init() {
self.totalSupply = 1000.0

self.VaultStoragePath = /storage/exampleTokenVault
self.VaultPublicPath = /public/exampleTokenVault
self.ReceiverPublicPath = /public/exampleTokenReceiver
self.AdminStoragePath = /storage/exampleTokenAdmin

// Create the Vault with the total supply of tokens and save it in storage
Expand All @@ -212,10 +210,10 @@ access(all) contract ExampleToken: FungibleToken {
// the `deposit` method and getAcceptedTypes method through the `Receiver` interface
// and the `balance` method through the `Balance` interface
//
let exampleTokenCap = self.account.capabilities.storage.issue<&ExampleToken.Vault>(vault.storagePath)
self.account.capabilities.publish(exampleTokenCap, at: vault.publicPath)
let receiverCap = self.account.capabilities.storage.issue<&ExampleToken.Vault>(vault.storagePath)
self.account.capabilities.publish(receiverCap, at: vault.receiverPath)
let exampleTokenCap = self.account.capabilities.storage.issue<&ExampleToken.Vault>(self.VaultStoragePath)
self.account.capabilities.publish(exampleTokenCap, at: self.VaultPublicPath)
let receiverCap = self.account.capabilities.storage.issue<&ExampleToken.Vault>(self.VaultStoragePath)
self.account.capabilities.publish(receiverCap, at: self.ReceiverPublicPath)

self.account.storage.save(<-vault, to: /storage/exampleTokenVault)

Expand Down
5 changes: 3 additions & 2 deletions contracts/FungibleToken.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ access(all) contract interface FungibleToken: ViewResolver {
///
access(all) fun deposit(from: @{Vault})

/// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts
/// getSupportedVaultTypes returns a dictionary of Vault types
/// and whether the type is currently supported by this Receiver
access(all) view fun getSupportedVaultTypes(): {Type: Bool}

/// Returns whether or not the given type is accepted by the Receiver
Expand Down Expand Up @@ -153,7 +154,7 @@ access(all) contract interface FungibleToken: ViewResolver {
self.balance = 0.0
}

/// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts
/// getSupportedVaultTypes returns a dictionary of vault types and whether this receiver accepts the indexed type
/// The default implementation is included here because vaults are expected
/// to only accepted their own type, so they have no need to provide an implementation
/// for this function
Expand Down
49 changes: 22 additions & 27 deletions contracts/FungibleTokenSwitchboard.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ access(all) contract FungibleTokenSwitchboard {
access(all) let PublicPath: PublicPath
access(all) let ReceiverPublicPath: PublicPath

access(all) entitlement Owner

/// The event that is emitted when a new vault capability is added to a
/// switchboard resource.
///
Expand All @@ -35,12 +37,11 @@ access(all) contract FungibleTokenSwitchboard {
/// deposit methods to deposit funds on it.
///
access(all) resource interface SwitchboardPublic {
access(all) fun getVaultTypes(): [Type]
access(all) view fun getVaultTypesWithAddress(): {Type: Address}
access(all) view fun getSupportedVaultTypes(): {Type: Bool}
access(all) view fun isSupportedVaultType(type: Type): Bool
access(all) fun deposit(from: @{FungibleToken.Vault})
access(all) fun safeDeposit(from: @{FungibleToken.Vault}): @{FungibleToken.Vault}?
access(all) view fun checkReceiverByType(type: Type): Bool
access(all) view fun safeBorrowByType(type: Type): &{FungibleToken.Receiver}?
}

Expand All @@ -62,7 +63,7 @@ access(all) contract FungibleTokenSwitchboard {
/// token vault deposit function through `{FungibleToken.Receiver}` that
/// will be added to the switchboard.
///
access(all) fun addNewVault(capability: Capability<&{FungibleToken.Receiver}>) {
access(Owner) fun addNewVault(capability: Capability<&{FungibleToken.Receiver}>) {
// Borrow a reference to the vault pointed to by the capability we
// want to store inside the switchboard
let vaultRef = capability.borrow()
Expand All @@ -89,7 +90,7 @@ access(all) contract FungibleTokenSwitchboard {
/// @param paths: The paths where the public capabilities are stored.
/// @param address: The address of the owner of the capabilities.
///
access(all) fun addNewVaultsByPath(paths: [PublicPath], address: Address) {
access(Owner) fun addNewVaultsByPath(paths: [PublicPath], address: Address) {
// Get the account where the public capabilities are stored
let owner = getAccount(address)
// For each path, get the saved capability and store it
Expand Down Expand Up @@ -132,7 +133,7 @@ access(all) contract FungibleTokenSwitchboard {
/// capability, rather than the `Type` from the reference borrowed from
/// said capability
///
access(all) fun addNewVaultWrapper(capability: Capability<&{FungibleToken.Receiver}>,
access(Owner) fun addNewVaultWrapper(capability: Capability<&{FungibleToken.Receiver}>,
type: Type) {
// Check if the capability is working
assert(capability.check(), message: "The passed capability is not valid")
Expand All @@ -158,7 +159,7 @@ access(all) contract FungibleTokenSwitchboard {
/// @param types: The types of the fungible token to be deposited on each path.
/// @param address: The address of the owner of the capabilities.
///
access(all) fun addNewVaultWrappersByPath(paths: [PublicPath], types: [Type],
access(Owner) fun addNewVaultWrappersByPath(paths: [PublicPath], types: [Type],
address: Address) {
// Get the account where the public capabilities are stored
let owner = getAccount(address)
Expand Down Expand Up @@ -189,7 +190,7 @@ access(all) contract FungibleTokenSwitchboard {
/// @param capability: The capability to a fungible token vault to be
/// removed from the switchboard.
///
access(all) fun removeVault(capability: Capability<&{FungibleToken.Receiver}>) {
access(Owner) fun removeVault(capability: Capability<&{FungibleToken.Receiver}>) {
// Borrow a reference to the vault pointed to by the capability we
// want to remove from the switchboard
let vaultRef = capability.borrow()
Expand Down Expand Up @@ -286,23 +287,6 @@ access(all) contract FungibleTokenSwitchboard {
return self.receiverCapabilities[type]!.borrow()
}

/// A getter function to know which tokens a certain switchboard
/// resource is prepared to receive.
///
/// @return The keys from the dictionary of stored
/// `{FungibleToken.Receiver}` capabilities that can be effectively
/// borrowed.
///
access(all) fun getVaultTypes(): [Type] {
let effectiveTypes: [Type] = []
for vaultType in self.receiverCapabilities.keys {
if self.receiverCapabilities[vaultType]!.check() {
effectiveTypes.append(vaultType)
}
}
return effectiveTypes
}

/// A getter function to know which tokens a certain switchboard
/// resource is prepared to receive along with the address where
/// those tokens will be deposited.
Expand All @@ -328,9 +312,20 @@ access(all) contract FungibleTokenSwitchboard {
/// @return Dictionary of FT types that can be deposited.
access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
let supportedVaults: {Type: Bool} = {}
for vaultType in self.receiverCapabilities.keys {
if self.receiverCapabilities[vaultType]!.check() {
supportedVaults[vaultType] = true
for receiverType in self.receiverCapabilities.keys {
if self.receiverCapabilities[receiverType]!.check() {
if receiverType.isSubtype(of: Type<@{FungibleToken.Vault}>()) {
supportedVaults[receiverType] = true
}
if receiverType.isSubtype(of: Type<@{FungibleToken.Receiver}>()) {
let receiverRef = self.receiverCapabilities[receiverType]!.borrow()!
let subReceiverSupportedTypes = receiverRef.getSupportedVaultTypes()
for subReceiverType in subReceiverSupportedTypes.keys {
if subReceiverType.isSubtype(of: Type<@{FungibleToken.Vault}>()) {
supportedVaults[subReceiverType] = true
}
}
}
}
}
return supportedVaults
Expand Down
11 changes: 10 additions & 1 deletion flow.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"aliases": {
"emulator": "0xee82856bf20e2aa6",
"testing": "0x0000000000000007",
"previewnet": "0xa0225e7000ac82a9",
"testnet": "0x9a0766d93b6608b7",
"mainnet": "0xf233dcee88fe0abe"
}
Expand All @@ -14,6 +15,7 @@
"aliases": {
"emulator": "0xf8d6e0586b0a20c7",
"testing": "0x0000000000000007",
"previewnet": "0xa0225e7000ac82a9",
"mainnet": "0xf233dcee88fe0abe",
"testnet": "0x9a0766d93b6608b7"
}
Expand All @@ -23,6 +25,7 @@
"aliases": {
"emulator": "0xf8d6e0586b0a20c7",
"testing": "0x0000000000000007",
"previewnet": "0xa0225e7000ac82a9",
"mainnet": "0xf233dcee88fe0abe",
"testnet": "0x9a0766d93b6608b7"
}
Expand Down Expand Up @@ -53,6 +56,7 @@
"aliases": {
"emulator": "0xf8d6e0586b0a20c7",
"testing": "0x0000000000000007",
"previewnet": "0xb6763b4399a888c8",
"mainnet": "0x1d7e57aa55817448",
"testnet": "0x631e88ae7f1d7c20"
}
Expand All @@ -61,14 +65,17 @@
"source": "./contracts/utility/Burner.cdc",
"aliases": {
"emulator": "0xf8d6e0586b0a20c7",
"testing": "0x0000000000000007"
"testing": "0x0000000000000007",
"previewnet": "0xa0225e7000ac82a9",
"testnet": "0x9a0766d93b6608b7"
}
},
"NonFungibleToken": {
"source": "./contracts/utility/NonFungibleToken.cdc",
"aliases": {
"emulator": "0xf8d6e0586b0a20c7",
"testing": "0x0000000000000007",
"previewnet": "0xb6763b4399a888c8",
"mainnet": "0x1d7e57aa55817448",
"testnet": "0x631e88ae7f1d7c20"
}
Expand All @@ -78,6 +85,7 @@
"aliases": {
"emulator": "0xf8d6e0586b0a20c7",
"testing": "0x0000000000000007",
"previewnet": "0xb6763b4399a888c8",
"mainnet": "0x1d7e57aa55817448",
"testnet": "0x631e88ae7f1d7c20"
}
Expand Down Expand Up @@ -108,6 +116,7 @@
"networks": {
"emulator": "127.0.0.1:3569",
"testing": "127.0.0.1:3569",
"previewnet": "access.previewnet.nodes.onflow.org:9000",
"testnet": "access.devnet.nodes.onflow.org:9000",
"mainnet": "access.mainnet.nodes.onflow.org:9000"
},
Expand Down
Loading

0 comments on commit 1519d10

Please sign in to comment.