Skip to content

Commit

Permalink
add burner and add event emission functions
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuahannan committed Jan 24, 2024
1 parent 9f24a73 commit 3886c24
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 42 deletions.
49 changes: 22 additions & 27 deletions contracts/ExampleToken.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,28 @@ access(all) contract ExampleToken: FungibleToken {
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")!
}

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

/// Called when a fungible token is burned via the `Burner.burn()` method
access(contract) fun burnCallback() {
if self.balance > 0.0 {
ExampleToken.totalSupply = ExampleToken.totalSupply - vault.getBalance()
}
self.balance = 0.0
}

access(all) view fun getViews(): [Type] {
return [
Type<FungibleTokenMetadataViews.FTView>(),
Expand Down Expand Up @@ -115,20 +137,6 @@ access(all) contract ExampleToken: FungibleToken {
return self.getSupportedVaultTypes()[type] ?? false
}

// 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")!
}

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

/// withdraw
///
/// Function that takes an amount as an argument
Expand Down Expand Up @@ -200,19 +208,6 @@ access(all) contract ExampleToken: FungibleToken {
return <- create Vault(balance: 0.0)
}

/// Function that destroys a Vault instance, effectively burning the tokens.
///
/// @param from: The Vault resource containing the tokens to burn
///
/// Will need to add an update to total supply
/// See https://github.com/onflow/flips/pull/131
access(all) fun burn(_ vault: @{FungibleToken.Vault}) {
if vault.balance > 0.0 {
ExampleToken.totalSupply = ExampleToken.totalSupply - vault.getBalance()
}
destroy vault
}

init() {
self.totalSupply = 1000.0

Expand Down
49 changes: 41 additions & 8 deletions contracts/FungibleToken.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ to the Provider interface.
*/

import ViewResolver from "ViewResolver"
import Burner from "Burner"

/// FungibleToken
///
Expand All @@ -48,16 +49,32 @@ access(all) contract interface FungibleToken: ViewResolver {
/// The event that is emitted when tokens are withdrawn from a Vault
/// ****TODO**** Add view helper functions so that this event can be emitted
/// only when the amount is non-zero and the from address is non-nil
access(all) event Withdrawn(amount: UFix64, type: String, from: Address?, fromUUID: UInt64, withdrawnUUID: UInt64)
access(all) event Withdrawn(type: String, amount: UFix64, from: Address?, fromUUID: UInt64, withdrawnUUID: UInt64)
access(self) view fun emitWithdrawnEvent(type: String, amount: UFix64, from: Address?, fromUUID: UInt64, withdrawnUUID: UInt64): Bool {
if (amount > 0.0) && (from != nil) && (from != 0x8624b52f9ddcd04a) | (from != 0x9eca2b38b18b5dfe) {
emit Withdrawn(type: type, amount: amount, from: from, fromUUID: fromUUID, withdrawnUUID: withdrawnUUID)
}
return true
}

/// The event that is emitted when tokens are deposited to a Vault
/// ****TODO**** Add view helper functions so that this event can be emitted
/// only when the amount is non-zero and the to address is non-nil
access(all) event Deposited(amount: UFix64, type: String, to: Address?, toUUID: UInt64, depositedUUID: UInt64)
access(all) event Deposited(type: String, amount: UFix64, to: Address?, toUUID: UInt64, depositedUUID: UInt64)
access(self) view fun emitDepositedEvent(type: String, amount: UFix64, to: Address?, toUUID: UInt64, depositedUUID: UInt64): Bool {
if (amount > 0.0) && (to != nil) && (to != 0x8624b52f9ddcd04a) | (to != 0x9eca2b38b18b5dfe) {
emit Deposited(type: type, amount: amount, to: to, toUUID: toUUID, depositedUUID: depositedUUID)
}
return true
}

/// Event that is emitted when the global burn method is called with a non-zero balance
/// ****TODO**** Add Burner contract so that this event can be emitted
access(all) event Burned(amount: UFix64, type: String, fromUUID: UInt64)
access(all) event Burned(type: String, amount: UFix64, fromUUID: UInt64)
acccess(self) view fun emitBurnedEvent(type: String, amount: UFix64, fromUUID: UInt64): Bool {
if amount > 0.0 {
emit Burned(type: type, amount: amount, fromUUID: fromUUID)
}
return true
}

/// Balance
///
Expand Down Expand Up @@ -92,7 +109,7 @@ access(all) contract interface FungibleToken: ViewResolver {
// `result` refers to the return value
result.getBalance() == amount:
"Withdrawal amount must be the same as the balance of the withdrawn Vault"
emit Withdrawn(amount: amount, type: self.getType().identifier, from: self.owner?.address, fromUUID: self.uuid, withdrawnUUID: result.uuid)
emitWithdrawnEvent(type: self.getType().identifier, amount: amount, from: self.owner?.address, fromUUID: self.uuid, withdrawnUUID: result.uuid)
}
}
}
Expand Down Expand Up @@ -126,14 +143,30 @@ access(all) contract interface FungibleToken: ViewResolver {
/// Ideally, this interface would also conform to Receiver, Balance, Transferor, Provider, and Resolver
/// but that is not supported yet
///
access(all) resource interface Vault: Receiver, Provider, Balance, ViewResolver.Resolver {
access(all) resource interface Vault: Receiver, Provider, Balance, ViewResolver.Resolver, Burner.Burnable {

/// Field that tracks the balance of a vault
access(all) var balance: UFix64

/// Get the balance of the vault
access(all) view fun getBalance(): UFix64

/// Called when a fungible token is burned via the `Burner.burn()` method
/// Implementations can do any bookkeeping or emit any events
/// that should be emitted when a vault is destroyed.
/// Many implementations will want to update the token's total supply
/// to reflect that the tokens have been burned and removed from the supply.
/// Implementations also need to set the balance to zero before the end of the function
/// This is to prevent vault owners from spamming fake Burned events.
access(contract) fun burnCallback() {
pre {
FungibleToken.emitBurnedEvent(type: self.getType().identifier, amount: self.balance, fromUUID: self.uuid)
}
post {
self.balance == 0.0: "The balance must be set to zero during the burnCallback method so that it cannot be spammed"
}
}

/// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts
/// 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
Expand Down Expand Up @@ -180,7 +213,7 @@ access(all) contract interface FungibleToken: ViewResolver {
pre {
from.isInstance(self.getType()):
"Cannot deposit an incompatible token type"
emit Deposited(amount: from.getBalance(), type: from.getType().identifier, to: self.owner?.address, toUUID: self.uuid, depositedUUID: from.uuid)
emitDepositedEvent(type: from.getType().identifier, amount: from.getBalance(), to: self.owner?.address, toUUID: self.uuid, depositedUUID: from.uuid)
}
post {
self.getBalance() == before(self.getBalance()) + before(from.getBalance()):
Expand Down
44 changes: 44 additions & 0 deletions contracts/utility/Burner.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// Burner is a contract that can facilitate the destruction of any resource on flow.
///
/// Contributors
/// - Austin Kline - https://twitter.com/austin_flowty
/// - Deniz Edincik - https://twitter.com/bluesign
/// - Bastian Müller - https://twitter.com/turbolent
access(all) contract Burner {
/// When Crescendo (Cadence 1.0) is released, custom destructors will be removed from cadece.
/// Burnable is an interface meant to replace this lost feature, allowing anyone to add a callback
/// method to ensure they do not destroy something which is not meant to be,
/// or to add logic based on destruction such as tracking the supply of a FT Collection
///
/// NOTE: The only way to see benefit from this interface
/// is to always use the burn method in this contract. Anyone who owns a resource can always elect **not**
/// to destroy a resource this way
access(all) resource interface Burnable {
access(contract) fun burnCallback()
}

/// burn is a global method which will destroy any resource it is given.
/// If the provided resource implements the Burnable interface,
/// it will call the burnCallback method and then destroy afterwards.
access(all) fun burn(_ r: @AnyResource) {
if let s <- r as @{Burnable} {
s.burnCallback()
destroy s
} else if let arr <- r as @[AnyResource] {
while arr.length > 0 {
let item <- arr.removeFirst()
self.burn(<-item)
}
destroy arr
} else if let dict <- r as @{HashableStruct: AnyResource} {
let keys = dict.keys
while keys.length > 0 {
let item <- dict.remove(key: keys.removeFirst())!
self.burn(<-item)
}
destroy dict
} else {
destroy r
}
}
}
12 changes: 11 additions & 1 deletion lib/go/contracts/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var (
placeholderMetadataViews = regexp.MustCompile(`"MetadataViews"`)
placeholderFTMetadataViews = regexp.MustCompile(`"FungibleTokenMetadataViews"`)
placeholderViewResolver = regexp.MustCompile(`"ViewResolver"`)
placeholderBurner = regexp.MustCompile(`"Burner"`)
)

const (
Expand All @@ -27,13 +28,15 @@ const (
filenameFTSwitchboard = "FungibleTokenSwitchboard.cdc"
filenameFTMetadataViews = "FungibleTokenMetadataViews.cdc"
filenameViewResolver = "utility/ViewResolver.cdc"
filenameBurner = "utility/Burner"
)

// FungibleToken returns the FungibleToken contract.
func FungibleToken(resolverAddr string) []byte {
func FungibleToken(resolverAddr, burnerAddr string) []byte {
code := assets.MustAssetString(filenameFungibleToken)

code = placeholderViewResolver.ReplaceAllString(code, "0x"+resolverAddr)
code = placeholderBurner.ReplaceAllString(code, "0x"+burnerAddr)

return []byte(code)
}
Expand Down Expand Up @@ -72,6 +75,13 @@ func ExampleToken(fungibleTokenAddr, metadataViewsAddr, ftMetadataViewsAddr, vie
return []byte(code)
}

// Burner returns the Burner contract.
func Burner() []byte {
code := assets.MustAssetString(filenameBurner)

return []byte(code)
}

// CustomToken returns the ExampleToken contract with a custom name.
//
// The returned contract will import the FungibleToken interface from the specified address.
Expand Down
Loading

0 comments on commit 3886c24

Please sign in to comment.