Skip to content

Commit

Permalink
add FlowToken (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
austinkline authored Oct 26, 2023
1 parent bcb6df4 commit baf710d
Show file tree
Hide file tree
Showing 7 changed files with 529 additions and 8 deletions.
48 changes: 44 additions & 4 deletions add.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ const {getImports} = require("./dependency-tree");

const specialContractsHandlers = {
"FungibleToken": (contract, userConfig, account) => {
console.log("FungibleToken requires some special setup. The account `emulator-ft` " +
return handleFungibleTokenAccountContract(contract, userConfig, account, "FungibleToken")
},
"FungibleTokenMetadataViews": (contract, userConfig, account) => {
return handleFungibleTokenAccountContract(contract, userConfig, account, "FungibleTokenMetadataViews")
},
"FlowToken": (contract, userConfig, account) => {
console.log("FlowToken requires some special setup. The account `emulator-flowtoken` " +
"will be created and the contract will be deployed to it on the emulator. \nGoing forward, any deployments to the " +
"flow emulator will require the --update flag to work correctly.")

const name = "FungibleToken"
const name = "FlowToken"

const serverPK = userConfig.accounts[account].key
const ftAccount = {
address: "ee82856bf20e2aa6", // this is the FungibleToken address on the flow emulator
address: "0ae53cb6e3f42a79", // this is the FungibleToken address on the flow emulator
key: serverPK
}
const emulatorAcct = "emulator-ft"
const emulatorAcct = "emulator-flowtoken"

// ensure emulator-ft is an account
userConfig.accounts[emulatorAcct] = ftAccount
Expand Down Expand Up @@ -131,6 +137,40 @@ const addAll = (path, account) => {
})
}

const handleFungibleTokenAccountContract = (contract, userConfig, account, contractName) => {
console.log(`FungibleToken requires some special setup. The account "emulator-ft"\n
will be created and the contract will be deployed to it on the emulator. \nGoing forward, any deployments to the\n
flow emulator will require the --update flag to work correctly.`)

const serverPK = userConfig.accounts[account].key
const ftAccount = {
address: "ee82856bf20e2aa6", // this is the FungibleToken address on the flow emulator
key: serverPK
}
const emulatorAcct = "emulator-ft"

// ensure emulator-ft is an account
userConfig.accounts[emulatorAcct] = ftAccount
if (!userConfig.deployments) {
userConfig.deployments = {}
}

// ensure that emulator-ft is a deployment account
if (!userConfig.deployments.emulator) {
userConfig.deployments.emulator = {}
}

if (!userConfig.deployments.emulator[emulatorAcct]) {
userConfig.deployments.emulator[emulatorAcct] = []
}

userConfig.contracts[contractName] = contract

if (!userConfig.deployments.emulator[emulatorAcct].includes(contractName)) {
userConfig.deployments.emulator[emulatorAcct].push(contractName)
}
}

module.exports = {
add,
addAll
Expand Down
274 changes: 274 additions & 0 deletions contracts/FlowToken.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
import "FungibleToken"
import "MetadataViews"
import "FungibleTokenMetadataViews"
import "ViewResolver"

pub contract FlowToken: FungibleToken, ViewResolver {

// Total supply of Flow tokens in existence
pub var totalSupply: UFix64

// Event that is emitted when the contract is created
pub event TokensInitialized(initialSupply: UFix64)

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

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

// Event that is emitted when new tokens are minted
pub event TokensMinted(amount: UFix64)

// Event that is emitted when tokens are destroyed
pub event TokensBurned(amount: UFix64)

// Event that is emitted when a new minter resource is created
pub event MinterCreated(allowedAmount: UFix64)

// Event that is emitted when a new burner resource is created
pub event BurnerCreated()

// 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.Provider, FungibleToken.Receiver, FungibleToken.Balance, MetadataViews.Resolver {

// holds the balance of a users tokens
pub var balance: UFix64

// initialize the balance at resource creation time
init(balance: UFix64) {
self.balance = balance
}

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

// 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: @FungibleToken.Vault) {
let vault <- from as! @FlowToken.Vault
self.balance = self.balance + vault.balance
emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
vault.balance = 0.0
destroy vault
}

destroy() {
if self.balance > 0.0 {
FlowToken.totalSupply = FlowToken.totalSupply - self.balance
}
}

/// Get all the Metadata Views implemented by FlowToken
///
/// @return An array of Types defining the implemented views. This value will be used by
/// developers to know which parameter to pass to the resolveView() method.
///
pub fun getViews(): [Type]{
return FlowToken.getViews()
}

/// Get a Metadata View from FlowToken
///
/// @param view: The Type of the desired view.
/// @return A structure representing the requested view.
///
pub fun resolveView(_ view: Type): AnyStruct? {
return FlowToken.resolveView(view)
}
}

// 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(): @FungibleToken.Vault {
return <-create Vault(balance: 0.0)
}

pub fun getViews(): [Type] {
return [Type<FungibleTokenMetadataViews.FTView>(),
Type<FungibleTokenMetadataViews.FTDisplay>(),
Type<FungibleTokenMetadataViews.FTVaultData>()]
}

/// Get a Metadata View from FlowToken
///
/// @param view: The Type of the desired view.
/// @return A structure representing the requested view.
///
pub fun resolveView(_ view: Type): AnyStruct? {
switch view {
case Type<FungibleTokenMetadataViews.FTView>():
return FungibleTokenMetadataViews.FTView(
ftDisplay: self.resolveView(Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
ftVaultData: self.resolveView(Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
)
case Type<FungibleTokenMetadataViews.FTDisplay>():
let media = MetadataViews.Media(
file: MetadataViews.HTTPFile(
url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
),
mediaType: "image/svg+xml"
)
let medias = MetadataViews.Medias([media])
return FungibleTokenMetadataViews.FTDisplay(
name: "FLOW Network Token",
symbol: "FLOW",
description: "FLOW is the protocol token that is required for transaction fees, storage fees, staking, and many applications built on the Flow Blockchain",
externalURL: MetadataViews.ExternalURL("https://flow.com"),
logos: medias,
socials: {
"twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain")
}
)
case Type<FungibleTokenMetadataViews.FTVaultData>():
return FungibleTokenMetadataViews.FTVaultData(
storagePath: /storage/flowTokenVault,
receiverPath: /public/flowTokenReceiver,
metadataPath: /public/flowTokenBalance,
providerPath: /private/flowTokenVault,
receiverLinkedType: Type<&FlowToken.Vault{FungibleToken.Receiver, FungibleToken.Balance, MetadataViews.Resolver}>(),
metadataLinkedType: Type<&FlowToken.Vault{FungibleToken.Balance, MetadataViews.Resolver}>(),
providerLinkedType: Type<&FlowToken.Vault{FungibleToken.Provider}>(),
createEmptyVaultFunction: (fun (): @FungibleToken.Vault {
return <-FlowToken.createEmptyVault()
})
)
}
return nil
}

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

// createNewBurner
//
// Function that creates and returns a new burner resource
//
pub fun createNewBurner(): @Burner {
emit BurnerCreated()
return <-create Burner()
}
}

// 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): @FlowToken.Vault {
pre {
amount > UFix64(0): "Amount minted must be greater than zero"
amount <= self.allowedAmount: "Amount minted must be less than the allowed amount"
}
FlowToken.totalSupply = FlowToken.totalSupply + amount
self.allowedAmount = self.allowedAmount - amount
emit TokensMinted(amount: amount)
return <-create Vault(balance: amount)
}

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

// Burner
//
// Resource object that token admin accounts can hold to burn tokens.
//
pub resource Burner {

// burnTokens
//
// Function that destroys a Vault instance, effectively burning the tokens.
//
// Note: the burned tokens are automatically subtracted from the
// total supply in the Vault destructor.
//
pub fun burnTokens(from: @FungibleToken.Vault) {
let vault <- from as! @FlowToken.Vault
let amount = vault.balance
destroy vault
emit TokensBurned(amount: amount)
}
}

init(adminAccount: AuthAccount) {
self.totalSupply = 0.0

// Create the Vault with the total supply of tokens and save it in storage
//
let vault <- create Vault(balance: self.totalSupply)
adminAccount.save(<-vault, to: /storage/flowTokenVault)

// Create a public capability to the stored Vault that only exposes
// the `deposit` method through the `Receiver` interface
//
adminAccount.link<&FlowToken.Vault{FungibleToken.Receiver, FungibleToken.Balance, MetadataViews.Resolver}>(
/public/flowTokenReceiver,
target: /storage/flowTokenVault
)

// Create a public capability to the stored Vault that only exposes
// the `balance` field through the `Balance` interface
//
adminAccount.link<&FlowToken.Vault{FungibleToken.Balance, MetadataViews.Resolver}>(
/public/flowTokenBalance,
target: /storage/flowTokenVault
)

let admin <- create Administrator()
adminAccount.save(<-admin, to: /storage/flowTokenAdmin)

// Emit an event that shows that the contract was initialized
emit TokensInitialized(initialSupply: self.totalSupply)
}
}
Loading

0 comments on commit baf710d

Please sign in to comment.