Skip to content

Commit

Permalink
Fungible Token Switchboard contract and tests (#71)
Browse files Browse the repository at this point in the history
* Switchboard contract skeleton

* Added test node_modules to gitignore

* example token first test

* example token mint tokens test

* example token transfer tokens test

* burn tokens test, comments and a little bit of code cleanup

* add switchboard manager interface, removecapabilty function header

* post conditions and prepare-execute (#68)

* First uncomented version of contract

* removed SwitchboardManager interface

* Switchboard resource creation and setup account tx

* Switchboard tests and folder restruct

* Transfer tokens through switchboard tx

* Packed all test in one file

* Added tx for adding and removing ft vault capabilities to switchboard

* Missing receiver public path

* Fixed addVaultCapability function, type of capability instead of type of reference was beeing used as dictionary key

* linking a capability just to a <&{FungibleToken.Receiver> rather than to a <&FungibleTokenSwitchboard.Switchboard{FungibleToken.Receiver}>

* Actually linking a Switchboard receiver, but keep borrowing an AnyResource receiver

* fixed test for removing capabilities

* Full contract comments, added deposit to SwitchboardPublic interface, changed SwitchboardReceiverPublicPath to the royalties standard path

* get vault capabilities script and test for it

* Commented properly every transaction

* go test passed

* Fixed removeVaultCapability function. emit VaultCapabilityAdded and VaultCapabilityRemoved in the corresponding functions

* Pushed bad flow.json file

* Move js test from project root to lib/js

* Rename path variable names

* Remove FungibleTokenSwitchboardInitialized event

* Fix typo in comment

* Fix more comment typos

* Simplify vault receivers fields and function names

* Make the receiverCapabilities field from switchboard resource private to contract

* Include js test in makefile

* Add node and npm modules to github ci

* Add dependencies lock file to github ci

* Add script test to package-lock.json

* Add npm install to lib/js/test makefile

* Add flow cli and npm dependencies to github actions

* Remove SwitchboardInitialized event

* Add assertions to balance involved tests

* Correct test name

* Change getVaultCapabilities for getVaultTypes

* Add safeDeposit function to switchboard. Add safe_transfer tx and tests

* Add first draft of addNewVaultsByPath function

* Add test for addVaultCapabilitiesByPath

* Add switchboard documentation

* Make addVault functions not to accept already present vaults

* Update comment

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

* Update comments

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

* Add optional owner parameters to vault events

* Update README.md

Line 199

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

* Update README.md

Line 203

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

* Update README.md

Line 209

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

* Update README.md

Line 239

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

* Update README.md

Line 240

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

* Update README.md

Line 272

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

* Update README.md

Line 303

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

* Make safeDeposit function return an optional Vault

* Fix ci

* Include receiverPath as a parameter for transfer_tokens transaction. WARNING: Test failing due to js test framework not supporting PublicPath as a parameter for transactions, feature has been tested on playground

* Add NotCompletedDeposit event to safeDeposit function

* Modify getVaultTypes method to return only the types of the capabilities that can be borrowed when the method is called

* Add review improvements

* Cleanup

* Polish comments

Co-authored-by: Joshua Hannan <[email protected]>
  • Loading branch information
alilloig and joshuahannan authored Aug 19, 2022
1 parent 3cfd76d commit 36abd68
Show file tree
Hide file tree
Showing 23 changed files with 19,558 additions and 15 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,18 @@ jobs:
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- uses: actions/setup-node@v3
with:
node-version: 14
cache: 'npm'
cache-dependency-path: lib/js/test/package-lock.json
- name: Install Flow CLI
run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" -- v0.33.1-sc-m5
- name: Flow cli Version
run: flow version
- name: Update PATH
run: echo "/root/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: cd lib/js/test && npm ci
- run: make ci

3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
.idea
node_modules
lib/js/test/node_modules/*
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
test:
$(MAKE) generate -C lib/go
$(MAKE) test -C lib/go
$(MAKE) test -C lib/js/test

.PHONY: ci
ci:
$(MAKE) ci -C lib/go
$(MAKE) ci -C lib/js/test
177 changes: 177 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,183 @@ To use the Flow Token contract as is, you need to follow these steps:
8. Use the `create_minter.cdc` transaction to create a new MintandBurn resource
and store it in a new Admin's account.

# Fungible Token Switchboard

`FungibleTokenSwitchboard.cdc`, allows users to receive payments in different fungible tokens using a single `&{FungibleToken.Receiver}` placed in a standard receiver path `/public/GenericFTReceiver`.

## How to use it

Users willing to use the Fungible Token Switchboard will need to setup their accounts by creating a new `FungibleTokenSwitchboard.Switchboard` resource and saving it to their accounts at the `FungibleTokenSwitchboard.StoragePath` path.

This can be acomplished by executing the transaction found in this repository `transactions/switchboard/setup_account.cdc`. This transaction will create and save a Switchboard resource to the signer's account,
and it also will create the needed public capabilities to access it. After setting up their switchboard, in order to make it support receiving a certain token, users will need to add the desired token's receiver capability to their switchboard resource.

## Adding a new vault to the switchboard
When a user wants to receive a new fungible token through their switchboard, they will need to add a new public capability linked to said FT to their switchboard resource. This can be accomplished in two different ways:

1. Adding a single capability using `addNewVault(capability: Capability<&{FungibleToken.Receiver}>)`
* Before calling this method on a transaction you should first retrieve the capability to the token's vault you are
willing to add to the switchboard, as is done in the template transaction `transactions/switchboard/add_vault_capabilty.cdc`.

```cadence
transaction {
let exampleTokenVaultCapabilty: Capability<&{FungibleToken.Receiver}>
let switchboardRef: &FungibleTokenSwitchboard.Switchboard
prepare(signer: AuthAccount) {
// Get the example token vault capability from the signer's account
self.exampleTokenVaultCapabilty =
signer.getCapability<&{FungibleToken.Receiver}>
(ExampleToken.ReceiverPublicPath)
// Get a reference to the signers switchboard
self.switchboardRef = signer.borrow<&FungibleTokenSwitchboard.Switchboard>
(from: FungibleTokenSwitchboard.StoragePath)
?? panic("Could not borrow reference to switchboard")
}
execute {
// Add the capability to the switchboard using addNewVault method
self.switchboardRef.addNewVault(capability: self.exampleTokenVaultCapabilty)
}
}
```
This function will panic if is not possible to `.borrow()` a reference to a `&{FungibleToken.Receiver}` from the passed capability. It will also panic if there is already a capability stored for the same `Type` of resource exposed by the capability.

2. Adding one or more capabilities using the paths where they are stored using `addNewVaultsByPath(paths: [PublicPath], address: Address)`
* When using this method, an array of `PublicPath` objects should be passed along with the `Address` of the account from where the vaults' capabilities should be retrieved.

```cadence
transaction (address: Address) {
let exampleTokenVaultPath: PublicPath
let vaultPaths: [PublicPath]
let switchboardRef: &FungibleTokenSwitchboard.Switchboard
prepare(signer: AuthAccount) {
// Get the example token vault path from the contract
self.exampleTokenVaultPath = ExampleToken.ReceiverPublicPath
// And store it in the array of public paths that will be passed to the
// switchboard method
self.vaultPaths = []
self.vaultPaths.append(self.exampleTokenVaultPath)
// Get a reference to the signers switchboard
self.switchboardRef = signer.borrow<&FungibleTokenSwitchboard.Switchboard>
(from: FungibleTokenSwitchboard.StoragePath)
?? panic("Could not borrow reference to switchboard")
}
execute {
// Add the capability to the switchboard using addNewVault method
self.switchboardRef.addNewVaultsByPath(paths: self.vaultPaths,
address: address)
}
}
```
This function won't panic, instead it will just not add to the `@Switchboard` any capability which can not be retrieved from any of the provided `PublicPath`s. It will also ignore any type of `&{FungibleToken.Receiver}` that is already present on the `@Switchboard`

## Removing a vault from the switchboard
If a user no longer wants to be able to receive deposits from a certain FT, or if they want to update the provided capability for one of them, they will need to remove the vault from the switchboard.
This can be accomplished by using `removeVault(capability: Capability<&{FungibleToken.Receiver}>)`.
This can be observed in the template transaction `transactions/switchboard/remove_vault_capability.cdc`:
```cadence
transaction {
let exampleTokenVaultCapabilty: Capability<&{FungibleToken.Receiver}>
let switchboardRef: &FungibleTokenSwitchboard.Switchboard
prepare(signer: AuthAccount) {
// Get the example token vault capability from the signer's account
self.exampleTokenVaultCapabilty = signer.getCapability
<&{FungibleToken.Receiver}>(ExampleToken.ReceiverPublicPath)
// Get a reference to the signers switchboard
self.switchboardRef = signer.borrow<&FungibleTokenSwitchboard.Switchboard>
(from: FungibleTokenSwitchboard.StoragePath)
?? panic("Could not borrow reference to switchboard")
}
execute {
// Remove the capability from the switchboard using the
// removeVault method
self.switchboardRef.removeVault(capability: self.exampleTokenVaultCapabilty)
}
}
```
This function will panic if is not possible to `.borrow()` a reference to a `&{FungibleToken.Receiver}` from the passed capability.

## Transfering tokens through the switchboard
The Fungible Token Switchboad provides two different ways of depositing tokens to it, using the `deposit(from: @FungibleToken.Vault)` method enforced by the `{FungibleToken.Receiver}` or using the `safeDeposit(from: @FungibleToken.Vault): @FungibleToken`:

1. Using the first method will be just the same as depositing to `&{FungibleToken.Receiver}`. The path for the Switchboard receiver is defined in `FungibleTokenSwitchboard.ReceiverPublicPath`,
the generic receiver path `/public/GenericFTReceiver` that can also be obtained from the NFT MetadataViews contract.
An example of how to do this can be found in the transaction template on this repo `transactions/switchboard/transfer_tokens.cdc`
```cadence
transaction(to: Address, amount: UFix64) {
// The Vault resource that holds the tokens that are being transferred
let sentVault: @FungibleToken.Vault
prepare(signer: AuthAccount) {
// Get a reference to the signer's stored vault
let vaultRef = signer.borrow<&ExampleToken.Vault>
(from: ExampleToken.VaultStoragePath)
?? panic("Could not borrow reference to the owner's Vault!")
// Withdraw tokens from the signer's stored vault
self.sentVault <- vaultRef.withdraw(amount: amount)
}
execute {
// Get the recipient's public account object
let recipient = getAccount(to)
// Get a reference to the recipient's Switchboard Receiver
let switchboardRef = recipient.getCapability
(FungibleTokenSwitchboard.ReceiverPublicPath)
.borrow<&{FungibleToken.Receiver}>()
?? panic("Could not borrow receiver reference to switchboard!")
// Deposit the withdrawn tokens in the recipient's switchboard receiver
switchboardRef.deposit(from: <-self.sentVault)
}
}
```

2. The `safeDeposit(from: @FungibleToken.Vault): @FungibleToken` works in a similar way, with the difference that it will not panic if the desired FT Vault can not be obtained from the Switchboard. The method will return the passed vault, empty if the funds were deposited sucessfully or still containing the funds if the transfer of the funds was not possible. Keep in mind that when using this method on a transaction you will allways have to deal with the returned resource. An example of this can be found on `transactions/switchboard/safe_transfer_tokens.cdc`:
```cadence
transaction(to: Address, amount: UFix64) {
// The reference to the vault from the payer's account
let vaultRef: &ExampleToken.Vault
// The Vault resource that holds the tokens that are being transferred
let sentVault: @FungibleToken.Vault
prepare(signer: AuthAccount) {
// Get a reference to the signer's stored vault
self.vaultRef = signer.borrow<&ExampleToken.Vault>(from: ExampleToken.VaultStoragePath)
?? panic("Could not borrow reference to the owner's Vault!")
// Withdraw tokens from the signer's stored vault
self.sentVault <- self.vaultRef.withdraw(amount: amount)
}
execute {
// Get the recipient's public account object
let recipient = getAccount(to)
// Get a reference to the recipient's Switchboard Receiver
let switchboardRef = recipient.getCapability(FungibleTokenSwitchboard.PublicPath)
.borrow<&FungibleTokenSwitchboard.Switchboard{FungibleTokenSwitchboard.SwitchboardPublic}>()
?? panic("Could not borrow receiver reference to switchboard!")
// Deposit the withdrawn tokens in the recipient's switchboard receiver,
// then deposit the returned vault in the signer's vault
self.vaultRef.deposit(from: <- switchboardRef.safeDeposit(from: <-self.sentVault))
}
}
```

# Running Automated Tests

Expand Down
Loading

0 comments on commit 36abd68

Please sign in to comment.