Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: V2 FungibleToken Standard #77

Closed
wants to merge 59 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
e7f717c
first real draft of v2 token standard
joshuahannan Jul 18, 2022
db9cc2d
Merge branch 'master' of github.com:onflow/flow-ft into new-standard
joshuahannan Jul 18, 2022
1718595
add transfer
joshuahannan Jul 18, 2022
03527c0
fix total supply field
joshuahannan Jul 18, 2022
5bf65e5
add createEmptyVault to resource definition
joshuahannan Jul 18, 2022
61d4243
PR comments
joshuahannan Jul 18, 2022
b39028c
updating paths
joshuahannan Jul 18, 2022
1bb20b6
change type array to dictionary, add transfer event, and use capabili…
joshuahannan Jul 19, 2022
1ae18bf
add conditional for destroy
joshuahannan Jul 21, 2022
7a102a8
Create FungibleTokenMetadataViews contract, include MetadataViews con…
alilloig Sep 1, 2022
32c0010
Apply suggestions from code review
alilloig Sep 2, 2022
093318d
Fix CI
alilloig Sep 2, 2022
079d367
Remove unnecesary init parameter on ftvaultdata
alilloig Sep 7, 2022
13b5b0d
Fix CI
alilloig Sep 7, 2022
fccd93c
Remove thumbnail and images from FTDisplay, add logo as only image an…
alilloig Sep 7, 2022
ba9bf2e
Merge branch 'alilloig/example-token-metadata-views' of github.com:on…
alilloig Sep 15, 2022
a6fdcb7
Restore files after the testing crisis
alilloig Sep 15, 2022
90395f4
Fix setup account from view test
alilloig Sep 15, 2022
f461448
Add auxiliary function for returning views and the case for returning…
alilloig Sep 15, 2022
ecc811e
Merge remote-tracking branch 'origin/master' into alilloig/ft-metadat…
alilloig Sep 15, 2022
306a1fe
Change logo (media) for logos (medias) at FTView
alilloig Sep 15, 2022
ead9c82
Update from logo to logos
alilloig Sep 15, 2022
e8b7f56
Fix spelling typos
alilloig Sep 16, 2022
58c157b
FT metadata docs section
alilloig Sep 19, 2022
c70204c
Add comments and use of getFTView function instead of resolveView
alilloig Sep 19, 2022
7aee404
Add scripts for read metadata
alilloig Sep 19, 2022
51bfed4
Merge remote-tracking branch 'origin/alilloig/example-token-metadata-…
alilloig Sep 19, 2022
1933db4
Finish docs
alilloig Sep 19, 2022
8d70279
Delete returnview functions
alilloig Sep 20, 2022
8c4a3c4
Switch to MetadataPublicPath
alilloig Sep 20, 2022
089b9fb
Add metadata path
alilloig Sep 20, 2022
7545b02
Merge remote-tracking branch 'origin/alilloig/ft-metadata-views' into…
alilloig Sep 20, 2022
4e5f87d
Change balance for metadata path at transactions. Fix FTVaultData con…
alilloig Sep 20, 2022
a0d375b
Add default implementation for MetadataViews.Resolver methods
alilloig Sep 20, 2022
1082bac
Merge remote-tracking branch 'origin/alilloig/ft-metadata-views' into…
alilloig Sep 20, 2022
0aadea8
Merge remote-tracking branch 'origin/alilloig/example-token-metadata-…
alilloig Sep 20, 2022
5b06fd7
Update contracts/FungibleTokenMetadataViews.cdc
alilloig Sep 30, 2022
21203f2
Update contracts/FungibleToken.cdc
alilloig Sep 30, 2022
5e2328d
Update contracts/FungibleToken.cdc
alilloig Sep 30, 2022
84880ef
Update contracts/FungibleTokenMetadataViews.cdc
alilloig Sep 30, 2022
eb5637a
Update contracts/FungibleTokenMetadataViews.cdc
alilloig Sep 30, 2022
e6de7ef
Update contracts/FungibleTokenMetadataViews.cdc
alilloig Sep 30, 2022
ad86e87
Update contracts/FungibleToken.cdc
alilloig Sep 30, 2022
81b0e9e
Update contracts/FungibleTokenMetadataViews.cdc
alilloig Sep 30, 2022
b58c49d
Update contracts/FungibleTokenMetadataViews.cdc
alilloig Sep 30, 2022
a31fd46
Update contracts/FungibleTokenMetadataViews.cdc
alilloig Sep 30, 2022
541764b
Update contracts/FungibleTokenMetadataViews.cdc
alilloig Sep 30, 2022
f24f5f2
Update contracts/FungibleTokenMetadataViews.cdc
alilloig Sep 30, 2022
50f777c
Update contracts/FungibleTokenMetadataViews.cdc
alilloig Sep 30, 2022
24763ca
Apply review suggestions
alilloig Sep 30, 2022
c5fd8b5
Add consistency about vaultData field name
alilloig Sep 30, 2022
54b8066
Merge
alilloig Sep 30, 2022
abe2080
move everything into the interface
joshuahannan Oct 4, 2022
2045a1d
Merge pull request #93 from onflow/alilloig/docs-metadata-views
alilloig Oct 5, 2022
1f32f2e
Merge pull request #91 from onflow/alilloig/example-token-metadata-views
alilloig Oct 5, 2022
e70eaaf
integrate metadata views
joshuahannan Oct 5, 2022
b924625
add metadata functions and optional returns
joshuahannan Dec 6, 2022
c742c6d
align with FLIP
joshuahannan Dec 16, 2022
051b966
WIP: V2 FungibleToken Standard (#126)
joshuahannan Mar 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 233 additions & 0 deletions contracts/ExampleToken-v2.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import FungibleToken from "./FungibleToken-v2.cdc"
import FungibleTokenInterface from "./FungibleToken-v2-ContractInterface.cdc"

pub contract ExampleToken: FungibleTokenInterface {

/// Total supply of ExampleTokens in existence
pub var totalSupply: {Type: UFix64}

/// Admin Path
pub let AdminStoragePath: StoragePath

/// EVENTS

/// We would like to be able to define events in the resource

/// TokensWithdrawn
///
/// The event that is emitted when tokens are withdrawn from a Vault
pub event TokensWithdrawn(amount: UFix64, from: Address?, type: Type)

/// TokensDeposited
///
/// The event that is emitted when tokens are deposited to a Vault
pub event TokensDeposited(amount: UFix64, to: Address?, type: Type)

/// TokensTransferred
///
/// The event that is emitted when tokens are transferred from one account to another
pub event TokensTransferred(amount: UFix64, from: Address?, to: Address?, type: Type)

/// TokensMinted
///
/// The event that is emitted when new tokens are minted
pub event TokensMinted(amount: UFix64, type: Type)

/// TokensBurned
///
/// The event that is emitted when tokens are destroyed
pub event TokensBurned(amount: UFix64, type: Type)

/// Function to return the types that the contract implements
pub fun getVaultTypes(): {Type: FungibleToken.VaultInfo} {
let typeDictionary: {Type: FungibleToken.VaultInfo} = {}

let vault <- create Vault(balance: 0.0)

typeDictionary[vault.getType()] = vault.getVaultInfo()

destroy vault

return typeDictionary
}

/// Vault
///
/// Each user stores an instance of only the Vault in their storage
/// The functions in the Vault and governed by the pre and post conditions
/// in FungibleToken when they are called.
/// The checks happen at runtime whenever a function is called.
///
/// Resources can only be created in the context of the contract that they
/// are defined in, so there is no way for a malicious user to create Vaults
/// out of thin air. A special Minter resource needs to be defined to mint
/// new tokens.
///
pub resource Vault: FungibleToken.Vault, FungibleToken.Provider, FungibleToken.Transferable, FungibleToken.Receiver, FungibleToken.Balance {

/// Storage and Public Paths
pub let StoragePath: StoragePath
pub let PublicReceiverBalancePath: PublicPath
pub let PrivateProviderPath: PrivatePath

/// The total balance of this vault
pub var balance: UFix64

// initialize the balance at resource creation time
init(balance: UFix64) {
self.balance = balance
self.StoragePath = /storage/exampleTokenVault
self.PublicReceiverBalancePath = /public/exampleTokenPublicPath
self.PrivateProviderPath = /private/exampleTokenProvider
}

/// Return information about the vault's type and paths
pub fun getVaultInfo(): FungibleToken.VaultInfo {
return FungibleToken.VaultInfo(type: self.getType(), StoragePath: self.StoragePath, PublicReceiverBalancePath: self.PublicReceiverBalancePath, PrivateProviderPath: self.PrivateProviderPath)
}

/// Get the balance of the vault
pub fun getBalance(): UFix64 {
return self.balance
}

/// withdraw
///
/// Function that takes an amount as an argument
/// and withdraws that amount from the Vault.
///
/// It creates a new temporary Vault that is used to hold
/// the tokens that are being transferred. It returns the newly
/// created Vault to the context that called so it can be deposited
/// elsewhere.
///
pub fun withdraw(amount: UFix64): @ExampleToken.Vault{FungibleToken.Vault} {
self.balance = self.balance - amount
emit TokensWithdrawn(amount: amount, from: self.owner?.address, type: self.getType())
return <-create Vault(balance: amount)
}

/// getAcceptedTypes optionally returns a list of vault types that this receiver accepts
pub fun getAcceptedTypes(): {Type: Bool} {
let types: {Type: Bool} = {}
types[Type<@ExampleToken.Vault>()] = true
return types
}

/// deposit
///
/// Function that takes a Vault object as an argument and adds
/// its balance to the balance of the owners Vault.
///
/// It is allowed to destroy the sent Vault because the Vault
/// was a temporary holder of the tokens. The Vault's balance has
/// been consumed and therefore can be destroyed.
///
pub fun deposit(from: @AnyResource{FungibleToken.Vault}) {
let vault <- from as! @ExampleToken.Vault
self.balance = self.balance + vault.balance
emit TokensDeposited(amount: vault.balance, to: self.owner?.address, type: self.getType())
vault.balance = 0.0
destroy vault
}

pub fun transfer(amount: UFix64, recipient: Capability<&{FungibleToken.Receiver}>) {
let transferVault <- self.withdraw(amount: amount)

// Get a reference to the recipient's Receiver
let receiverRef = recipient.borrow()
?? panic("Could not borrow receiver reference to the recipient's Vault")

// Deposit the withdrawn tokens in the recipient's receiver
receiverRef.deposit(from: <-transferVault)

emit TokensTransferred(amount: amount, from: self.owner?.address, to: receiverRef.owner?.address, type: self.getType())
}

/// createEmptyVault
///
/// Function that creates a new Vault with a balance of zero
/// and returns it to the calling context. A user must call this function
/// and store the returned Vault in their storage in order to allow their
/// account to be able to receive deposits of this token type.
///
pub fun createEmptyVault(): @ExampleToken.Vault{FungibleToken.Vault} {
return <-create Vault(balance: 0.0)
}

destroy() {
if self.balance > 0.0 {
ExampleToken.totalSupply[self.getType()] = ExampleToken.totalSupply[self.getType()]! - self.balance
}
}
}

pub resource Administrator {

/// createNewMinter
///
/// Function that creates and returns a new minter resource
///
pub fun createNewMinter(allowedAmount: UFix64): @Minter {
return <-create Minter(allowedAmount: allowedAmount)
}
}

/// Minter
///
/// Resource object that token admin accounts can hold to mint new tokens.
///
pub resource Minter {

/// The amount of tokens that the minter is allowed to mint
pub var allowedAmount: UFix64

/// mintTokens
///
/// Function that mints new tokens, adds them to the total supply,
/// and returns them to the calling context.
///
pub fun mintTokens(amount: UFix64): @ExampleToken.Vault {
pre {
amount > 0.0: "Amount minted must be greater than zero"
amount <= self.allowedAmount: "Amount minted must be less than the allowed amount"
}
ExampleToken.totalSupply[self.getType()] = ExampleToken.totalSupply[self.getType()]! + amount
self.allowedAmount = self.allowedAmount - amount
emit TokensMinted(amount: amount, type: self.getType())
return <-create Vault(balance: amount)
}

init(allowedAmount: UFix64) {
self.allowedAmount = allowedAmount
}
}

init() {
self.totalSupply = {}
self.totalSupply[Type<@ExampleToken.Vault>()] = 1000.0

self.AdminStoragePath = /storage/exampleTokenAdmin

// Create the Vault with the total supply of tokens and save it in storage
//
let vault <- create Vault(balance: self.totalSupply[Type<@ExampleToken.Vault>()]!)

let storagePath = vault.StoragePath
let receiverBalancePath = vault.PublicReceiverBalancePath

self.account.save(<-vault, to: storagePath)

// Create a public capability to the stored Vault that exposes
// the `deposit` method and getAcceptedTypes method through the `Receiver` interface
// and the `getBalance()` method through the `Balance` interface
//
self.account.link<&{FungibleToken.Receiver, FungibleToken.Balance}>(
receiverBalancePath,
target: storagePath
)

let admin <- create Administrator()
self.account.save(<-admin, to: self.AdminStoragePath)
}
}
34 changes: 34 additions & 0 deletions contracts/FungibleToken-v2-ContractInterface.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import FungibleToken from "./FungibleToken-v2.cdc"

pub contract interface FungibleTokenInterface {
joshuahannan marked this conversation as resolved.
Show resolved Hide resolved

/// TokensWithdrawn
///
/// The event that is emitted when tokens are withdrawn from a Vault
pub event TokensWithdrawn(amount: UFix64, from: Address?, type: Type)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Highly Opinionated Views -

  • We don't need the prefix Tokens in events as it is obvious that Fungible token would only emit events related to tokens by adding the prefix; it creates confusion that FT does support besides the tokens.
  • type can be the first parameter as it is a differentiator among different tokens supported by the contract. It would be cleaner that way, and event filters can use that as a standard to be the first param of the event.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need the prefix Tokens in events as it is obvious that Fungible token would only emit events related to tokens by adding the prefix; it creates confusion that FT does support besides the tokens.

Are these events coming from the FT contract itself, though? For events, I feel that we want to be as explicit as possible because we do not know what other contracts will name themselves and they will all need to define these events individually (unless that is changing?). For instance, what if FiatToken had instead been named USDC? Now the event signature is just Withdrawn which isn't necessarily as clear anymore. It also helps differentiate from a common event name because Withdrawn is much more likely to be used elsewhere than TokensWithdrawn so any indexer trying to make sense of events is going to have less trouble

type can be the first parameter as it is a differentiator among different tokens supported by the contract. It would be cleaner that way, and event filters can use that as a standard to be the first param of the event.

I like this one 🙂

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these events coming from the FT contract itself, though? For events, I feel that we want to be as explicit as possible because we do not know what other contracts will name themselves and they will all need to define these events individually (unless that is changing?). For instance, what if FiatToken had instead been named USDC? Now the event signature is just Withdrawn which isn't necessarily as clear anymore. It also helps differentiate from a common event name because Withdrawn is much more likely to be used elsewhere than TokensWithdrawn so any indexer trying to make sense of events is going to have less trouble

Events should always be standardised within the contract. If some FT is not implementing the standardized events, then those tokens are not compliant. Any other name is not compliant with FT standards. We can't restrict another contract to using the same event name, They can use the TokensWithdrawn or Withdrawn, but the context is automatically different as the contract address differs, so the indexer should not have a problem indexing the events.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Events should always be standardised within the contract. If some FT is not implementing the standardized events, then those tokens are not compliant. Any other name is not compliant with FT standards. We can't restrict another contract to using the same event name, They can use the TokensWithdrawn or Withdrawn, but the context is automatically different as the contract address differs, so the indexer should not have a problem indexing the events.

Not sure I follow, is the event now coming from the FungibleToken address? Otherwise I think I need more info about what you're saying.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I did it like this was because I wanted to keep the events as similar to their old versions as possible. Adding the argument onto the end would not break off-chain event listener code because it just adds another element to the array instead of replacing an existing element. I also would prefer to keep the same name for the same reason.


/// TokensDeposited
///
/// The event that is emitted when tokens are deposited to a Vault
pub event TokensDeposited(amount: UFix64, to: Address?, type: Type)

/// TokensTransferred
///
/// The event that is emitted when tokens are transferred from one account to another
pub event TokensTransferred(amount: UFix64, from: Address?, to: Address?, type: Type)

/// TokensMinted
///
/// The event that is emitted when new tokens are minted
pub event TokensMinted(amount: UFix64, type: Type)

/// Contains the total supply of the fungible token
pub var totalSupply: {Type: UFix64}

/// Function to return the types that the contract implements
pub fun getVaultTypes(): {Type: FungibleToken.VaultInfo} {
post {
result.length > 0: "Must indicate what fungible token types this contract defines"
}
}
}
Loading