flow-ft icon indicating copy to clipboard operation
flow-ft copied to clipboard

FEATURE: Fungible Token Switchboard Contract

Open joshuahannan opened this issue 2 years ago • 11 comments

Issue To Be Solved

Currently, if a user wants to receive a particular fungible token, they must run a specific setup transaction to initialize the specific vault in their account. This is a bit awkward for users who want to pay someone in a different fungible token than the one they have specified. This problem is especially pronounced in the discussion of how royalties should be paid for NFT sales. A user provides a {FungibleToken.Receiver} capability to receive royalties from, but if the sale is performed with a different token type than the one specified, the marketplace has to either look for the other token type through the receiver's owner.address field, or just not pay royalties, which is not ideal.

Want a solution that will allow anyone to deposit a generic fungible token into a standard receiver path that will forward the tokens to the correct vault in the account. The solution should be as generic and scalable as possible while not exposing any users to any ddos-esque risk or security risk.

Suggest A Solution

  • [ ] Create a design document for the smart contract and write an initial proposal. Post the proposal here.
  • [ ] Discuss with the community and Flow team members to align on important features.
  • [ ] Within this epic, create and estimate issues for:
  • The development and testing of the contract
  • The documentation of the contract and transactions

Needs to integrate with the proposed solution for Royalties with the generic receiver

Design proposal Document in Notion

Unanswered Questions

Would the contract have a strict list of fungible tokens that it supports that an admin can update when needed, or would we leave that in the hands of the owner to specify which fungible tokens they want to support?

Would the resource create a new vault for a user when someone tries to send them tokens that their account doesn't already support?

Context

This is preventing royalties from being as useful and powerful as possible

joshuahannan avatar Apr 29 '22 18:04 joshuahannan

Would the resource create a new vault for a user when someone tries to send them tokens that their account doesn't already support?

This unfortunately cannot be because of storage fees :( With permission less deployment it is a very dangerous DoS opportunity on accounts.

Would the contract have a strict list of fungible tokens that it supports that an admin can update when needed, or would we leave that in the hands of the owner to specify which fungible tokens they want to support?

This is currently the best option, another idea we recently discussed was the sender to pay for storage fees. And making this contract both for NFT and FT.

bluesign avatar Apr 29 '22 18:04 bluesign

another idea we recently discussed was the sender to pay for storage fees. And making this contract both for NFT and FT.

Can you elaborate? Do you mean that if the contract does create and store a new vault if an account doesn't support it, we could have the sender also send some FLOW to pay for it so we can still support that?

joshuahannan avatar May 09 '22 15:05 joshuahannan

@joshuahannan yeah , basically, if someone is sending me something new, there can be a function on switchboard contract where they can pay for the storage. Something like deposit with 2 parameters, NFT and some flow token vault, which will return excess flow tokens vault.

So this can allow some kind of airdrops if sender pays the storage cost.

Blocker I see is only cadence crashing on type from removed contract. ( but probably will be fixed with secure cadence or stable one )

bluesign avatar May 10 '22 14:05 bluesign

Here is the design proposal for the FT Switchboard, please @bluesign let me know your thoughts on it!

Fungible Token Switchboard Contract Design Document

Fungible Token Switchboard

Owner: @Álvaro Lillo

Secondary Owner: @Joshua Hannan

Repo Location: https://github.com/onflow/flow-ft

Testnet Address:

Mainnet Address:

Objective

If a user wants to receive a particular fungible token, they must run a specific setup transaction to initialize the specific vault in their account. We want to allow anyone to deposit a generic fungible token into a standard receiver path that will forward the tokens to the correct vault in the account.

Motivation

This is a bit awkward for users who want to pay someone in a different fungible token than the one they have specified. This problem is especially pronounced in the discussion of how royalties should be paid for NFT sales.

User Benefit

Users will be able to confidently know that they can transact with multiple token types through the same receiver. The Flow community can begin to standardize fungible token transactions so that projects and users who need to send a new token to an account do not have to keep track of every single path and contract that want to potentially use to deposit. Users can also control what tokens they are comfortable accepting through a standard interface instead of having to use multiple contracts’ boilerplate transactions.

Additionally, this solution directly benefits users who want to receive royalties for NFT purchases. Right now if the beneficiary of a sale cut (as a royalty beneficiary) was going to receive a payment of a different FT token than the one from the capability they set up as receiver the transaction would be aborted by the FT deposit function pre-condition:

// Assert that the concrete type of the deposited vault is the same
// as the vault that is accepting the deposit
pre {
	from.isInstance(self.getType()): 
      "Cannot deposit an incompatible token type"
 }

Creating a new resource implementing {FungibleToken.Receiver} will allow NFT royalties receivers to provide a generic receiver that could receive a variety of FTs instead of just one.

Design Proposal

We will write a new contract “FungibleTokenSwitchboard” that will feature a resource “Switchboard” conforming to {FungibleToken.Receiver} but instead of storing a vault balance, it will store a record of these capabilities: Capability<&AnyResource{FungibleToken.Receiver}>.

The deposit function enforced by the {FungibleToken.Receiver} will then check the type of the @FungibleToken.Vault received as an argument and route it to the proper Capability<&AnyResource{FungibleToken.Receiver}>. If there is no capability matching the deposited FT, the transaction will fail. I believe we should explore the idea exposed by bluesign on the github issue about allowing payers to pay for the creation of a new Vault of a FT no yet used by the seller. Probably this will make a nice topic of discussion with the community, to see if this will be a desired feature. This would be a deposit function with an extra argument for a @FlowToken.Vault for paying for the transaction fees from creating the new vault, something like:

pub fun depositNewVaultWithStorageFees(from: @FungibleToken.Vault, fees: @FlowToken.Vault)

The owner of the Switchboard resource will be able to call the function addVaultCapability(switch: Capability<&{FungibleToken.Receiver}> in order to store a new one of these capabilities.

The Switchboard resource also should expose a getVaultCapabilities(): [Capability<&{FungibleToken.Receiver}>] in order to allow buyers to know if the token they are willing to use for the payment is supported by the seller / payment receiver.

Drawbacks

In a first version of this contract, only allowing payers to deposit certain types of FTs approved by the seller I believe there won’t be any drawbacks, as payment receivers could use this as a regular {FungibleToken.Receiver} in a transparent way for any other contracts involved in the transaction.

However if we explore the possibility of allowing payers to create new Vaults into sellers accounts storage fees will be needed to take into account, in order to avoid any possible ddos-esque risk for sellers that could see their accounts flooded by unwanted Vaults.

If the contract can also create new FTs in a user’s account, it would not be able to create the usual receivers and capabilities unless it had access to the AuthAccount, which probably would not be good.

Performance Implications

Could a Switchboard owner set deliberately a too long list of capabilities that will make checking for the proper one an unnecessary waste of computing increasing the cost of the transaction?

Dependencies

The only thing that will directly depend on the switchboard out of the gate is [the setup that we proposed for royalties](https://github.com/onflow/flow-nft#important-royalty-instructions-for-royalty-receivers) but since this is for any fungible token, any transaction or project that sends tokens would need to be made aware of this. it isn’t a breaking change for them, but could be beneficial for them to know about

Engineering Impact

The contract will be maintained and tested by @Álvaro Lillo

Best Practices

Cadence design patterns and anti-patterns should be observed during the coding of this contract, otherwise this will not affect any best practices of developing Flow.

Tutorials and Examples

Working examples on how to use this contract and the Switchboard resource will be included in the onflow/flow-ft repo showing developers how to utilize it instead of a traditional Vault resource.

Compatibility

Thanks to Cadence capabilities/interfaces this contract will be fully interoperable with any other older contract.

User Impact

Marketplaces will be able to expand their payment options for NFTs #onFlow, so probably main concern here is keeping them informed about the new feature.

Questions and Discussion Topics

As exposed before, the possibility of allowing payers to deposit absolutely any FT paying for the vault creation should be discussed with the community to see if that’s a desirable feature.

alilloig avatar May 18 '22 17:05 alilloig

@alilloig nice proposal. Unfortunately after thinking on sender pays storage part, I concluded that there is currently no way to enforce that with cadence. I think we can remove that part.

Capability solution is looking very nice, can't wait to see the contract.

bluesign avatar May 31 '22 14:05 bluesign

@bluesign Why don't you think you can enforce that with Cadence? You could just use a function like this:

pub fun depositNewVaultWithStorageFees(from: @FungibleToken.Vault, fees: @FlowToken.Vault)

The resource could store it temporarily, then if the owner wants to store it in their actual account, they could run a separate transaction to initialize the storage path properly. This deposit function might also need an import address and contract name argument so the owner knows which contract to work with, but it still seems possible to me.

joshuahannan avatar May 31 '22 15:05 joshuahannan

@joshuahannan It is possible maybe but I saw problems when trying some attack scenarios:

  • How to calculate the fee ? We can do naive storageUsed before and storageUsed after calculation, but it is totally avoidable. Then I can fill up your storage to the last byte. ( which is really bad experience )

Imagine you can stop someone from getting royalties etc.

  • How to get rid of unwanted Vault? What if the Contract owner puts a panic on destroy ? Though this is a generic problem I guess, not sure if it is still possible after secure cadence.

I mean on happy path it is working good, but there are few risks like above.

bluesign avatar May 31 '22 17:05 bluesign

How to calculate the fee ? We can do naive storageUsed before and storageUsed after calculation, but it is totally avoidable. Then I can fill up your storage to the last byte. ( which is really bad experience )

I think we can pretty easily calculate the size of a typical capability and vault and just charge an amount of FLOW that is equivalent to that, even if it doesn't cause the storage to overflow.

How to get rid of unwanted Vault?

Just put a method on the switchboard resource that destroys an unwanted vault. I think we'll just have to accept the risk that there might be a panic on destroy

joshuahannan avatar May 31 '22 18:05 joshuahannan

I agree, on happy path it works, though when we open the gates, generic receiver is pretty risky.

I can even deploy contract, create some Fungible, send to your switchboard, then delete contract. Then your switchboard probably dies.

I think maybe after this stuff fixed can be, but I see now it is very dangerous.

bluesign avatar May 31 '22 19:05 bluesign

I can even deploy contract, create some Fungible, send to your switchboard, then delete contract. Then your switchboard probably dies.

Would that break the entire switchboard? Seems like that would only break the one vault that belongs to the contract that was removed. I've never tried something like that before though, so I don't know for sure.

joshuahannan avatar Jun 01 '22 17:06 joshuahannan

I think depends on how we store, if we store in resource field ( as array or dictionary ) , breaks all switchboard I guess. If we store on storage separate and load on demand, I think only that vault.

bluesign avatar Jun 01 '22 18:06 bluesign