Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions cadence/contracts/internal-dependencies/interfaces/DFB.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,13 @@ access(all) contract DFB {
}
}
}

/// An interface for a price oracle adapter. Implementations should adapt this interface to various price feed
/// oracles deployed on Flow
access(all) struct interface PriceOracle {
/// Returns the asset type serving as the price basis - e.g. USD in FLOW/USD
access(all) view fun unitOfAccount(): Type
/// Returns the latest price data for a given asset denominated in unitOfAccount()
access(all) fun price(ofToken: Type): UFix64
}
}
217 changes: 217 additions & 0 deletions cadence/contracts/internal-dependencies/tokens/USDA.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import "FungibleToken"
import "MetadataViews"
import "FungibleTokenMetadataViews"

///
/// THIS CONTRACT IS A MOCK AND IS NOT INTENDED FOR USE IN PRODUCTION
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
///
access(all) contract USDA : FungibleToken {

/// Total supply of USDA in existence
access(all) var totalSupply: UFix64

/// Storage and Public Paths
access(all) let VaultStoragePath: StoragePath
access(all) let VaultPublicPath: PublicPath
access(all) let ReceiverPublicPath: PublicPath
access(all) let AdminStoragePath: StoragePath

/// The event that is emitted when new tokens are minted
access(all) event Minted(type: String, amount: UFix64, toUUID: UInt64, minterUUID: UInt64)
/// Emitted whenever a new Minter is created
access(all) event MinterCreated(uuid: UInt64)

/// 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.
///
access(all) fun createEmptyVault(vaultType: Type): @USDA.Vault {
return <- create Vault(balance: 0.0)
}

access(all) view fun getContractViews(resourceType: Type?): [Type] {
return [
Type<FungibleTokenMetadataViews.FTView>(),
Type<FungibleTokenMetadataViews.FTDisplay>(),
Type<FungibleTokenMetadataViews.FTVaultData>(),
Type<FungibleTokenMetadataViews.TotalSupply>()
]
}

access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
switch viewType {
case Type<FungibleTokenMetadataViews.FTView>():
return FungibleTokenMetadataViews.FTView(
ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
ftVaultData: self.resolveContractView(resourceType: nil, viewType: 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: "AlpenFlow USD",
symbol: "USDA",
description: "A mocked version of AlpenFlow USD",
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: self.VaultStoragePath,
receiverPath: self.ReceiverPublicPath,
metadataPath: self.VaultPublicPath,
receiverLinkedType: Type<&USDA.Vault>(),
metadataLinkedType: Type<&USDA.Vault>(),
createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
return <-USDA.createEmptyVault(vaultType: Type<@USDA.Vault>())
})
)
case Type<FungibleTokenMetadataViews.TotalSupply>():
return FungibleTokenMetadataViews.TotalSupply(
totalSupply: USDA.totalSupply
)
}
return nil
}

/* --- CONSTRUCTS --- */

/// 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.
///
access(all) resource Vault: FungibleToken.Vault {

/// The total balance of this vault
access(all) var balance: UFix64

/// Identifies the destruction of a Vault even when destroyed outside of Buner.burn() scope
access(all) event ResourceDestroyed(uuid: UInt64 = self.uuid, balance: UFix64 = self.balance)

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

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

access(all) view fun getViews(): [Type] {
return USDA.getContractViews(resourceType: nil)
}

access(all) fun resolveView(_ view: Type): AnyStruct? {
return USDA.resolveContractView(resourceType: nil, viewType: view)
}

access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
let supportedTypes: {Type: Bool} = {}
supportedTypes[self.getType()] = true
return supportedTypes
}

access(all) view fun isSupportedVaultType(type: Type): Bool {
return self.getSupportedVaultTypes()[type] ?? false
}

access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
return amount <= self.balance
}

access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @USDA.Vault {
self.balance = self.balance - amount
return <-create Vault(balance: amount)
}

access(all) fun deposit(from: @{FungibleToken.Vault}) {
let vault <- from as! @USDA.Vault
self.balance = self.balance + vault.balance
destroy vault
}

access(all) fun createEmptyVault(): @USDA.Vault {
return <-create Vault(balance: 0.0)
}
}

/// Minter
///
/// Resource object that token admin accounts can hold to mint new tokens.
///
access(all) resource Minter {
/// Identifies when a Minter is destroyed, coupling with MinterCreated event to trace Minter UUIDs
access(all) event ResourceDestroyed(uuid: UInt64 = self.uuid)

init() {
emit MinterCreated(uuid: self.uuid)
}

/// mintTokens
///
/// Function that mints new tokens, adds them to the total supply,
/// and returns them to the calling context.
///
access(all) fun mintTokens(amount: UFix64): @USDA.Vault {
USDA.totalSupply = USDA.totalSupply + amount
let vault <-create Vault(balance: amount)
emit Minted(type: vault.getType().identifier, amount: amount, toUUID: vault.uuid, minterUUID: self.uuid)
return <-vault
}
}

init() {
let initialMint = 1000.0

self.totalSupply = 0.0

let address = self.account.address
self.VaultStoragePath = StoragePath(identifier: "usdaTokenVault_\(address)")!
self.VaultPublicPath = PublicPath(identifier: "usdaTokenVault_\(address)")!
self.ReceiverPublicPath = PublicPath(identifier: "usdaTokenReceiver_\(address)")!
self.AdminStoragePath = StoragePath(identifier: "usdaTokenAdmin_\(address)")!


// Create a public capability to the stored Vault that exposes
// the `deposit` method and getAcceptedTypes method through the `Receiver` interface
// and the `balance` method through the `Balance` interface
//
self.account.storage.save(<-create Vault(balance: self.totalSupply), to: self.VaultStoragePath)
let vaultCap = self.account.capabilities.storage.issue<&USDA.Vault>(self.VaultStoragePath)
self.account.capabilities.publish(vaultCap, at: self.VaultPublicPath)
let receiverCap = self.account.capabilities.storage.issue<&USDA.Vault>(self.VaultStoragePath)
self.account.capabilities.publish(receiverCap, at: self.ReceiverPublicPath)

// Create a Minter & mint the initial supply of tokens to the contract account's Vault
let admin <- create Minter()

self.account.capabilities.borrow<&Vault>(self.ReceiverPublicPath)!.deposit(
from: <- admin.mintTokens(amount: initialMint)
)

self.account.storage.save(<-admin, to: self.AdminStoragePath)
}
}
69 changes: 69 additions & 0 deletions cadence/contracts/mocks/MockOracle.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import "FungibleToken"

import "DFB"

///
/// THIS CONTRACT IS A MOCK AND IS NOT INTENDED FOR USE IN PRODUCTION
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
///
access(all) contract MockOracle {

/// token price denominated in USD
access(self) let mockedPrices: {Type: UFix64}
/// the token type in which prices are denominated
access(self) let unitOfAccount: Type
/// bps up or down by which current price moves when bumpPrice is called
access(self) let bumpVariance: UInt16

access(all) struct PriceOracle : DFB.PriceOracle {

/// Returns the asset type serving as the price basis - e.g. USD in FLOW/USD
access(all) view fun unitOfAccount(): Type {
return MockOracle.unitOfAccount
}

/// Returns the latest price data for a given asset denominated in unitOfAccount()
access(all) fun price(ofToken: Type): UFix64 {
if ofToken == self.unitOfAccount() {
return 1.0
}
return MockOracle.mockedPrices[ofToken] ?? panic("Unsupported token type \(ofToken.identifier)")
}
}

// resets the price of the token within 0-bumpVariance (bps) of the current price
// allows for mocked data to have variability
access(all) fun bumpPrice(forToken: Type) {
if forToken == self.unitOfAccount {
return
}
let current = self.mockedPrices[forToken]
?? panic("MockOracle does not have a price set for token \(forToken.identifier)")
let sign = revertibleRandom<UInt8>(modulo: 2) // 0 - down | 1 - up
let variance = self.convertToBPS(revertibleRandom<UInt16>(modulo: self.bumpVariance)) // bps up or down
if sign == 0 {
self.mockedPrices[forToken] = current - (current * variance)
} else {
self.mockedPrices[forToken] = current + (current * variance)
}
}

access(all) fun setPrice(forToken: Type, price: UFix64) {
self.mockedPrices[forToken] = price
}

access(self) view fun convertToBPS(_ variance: UInt16): UFix64 {
var res = UFix64(variance)
for i in InclusiveRange(0, 3) {
res = res / 10.0
}
return res
}

init(unitOfAccountIdentifier: String) {
self.mockedPrices = {}
// e.g. vault.getType().identifier == 'A.0ae53cb6e3f42a79.FlowToken.Vault'
self.unitOfAccount = CompositeType(unitOfAccountIdentifier) ?? panic("Invalid unitOfAccountIdentifier \(unitOfAccountIdentifier)")
self.bumpVariance = 100 // 0.1% variance
}
}
12 changes: 12 additions & 0 deletions cadence/scripts/mocks/oracle/get_price.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import "MockOracle"

/// Gets the mocked price data from the MockOracle contract denominated in the current unitOfAccount token type
///
/// @param forTokenIdentifier: The Vault Type identifier for the token whose price will be retrieved
access(all)
fun main(forTokenIdentifier: String): UFix64 {
// Type identifier - e.g. vault.getType().identifier == 'A.0ae53cb6e3f42a79.FlowToken.Vault'
return MockOracle.PriceOracle().price(
ofToken: CompositeType(forTokenIdentifier) ?? panic("Invalid forTokenIdentifier \(forTokenIdentifier)")
)
}
18 changes: 18 additions & 0 deletions cadence/transactions/mocks/oracle/bump_price.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import "MockOracle"

/// Bumps the current token price in the MockOracle contract by some percentage (up or down) between
/// 0-bumpVariance (default 1%) randomly chosen using revertibleRandom
///
/// @param vaultIdentifier: The Vault's Type identifier
/// e.g. vault.getType().identifier == 'A.0ae53cb6e3f42a79.FlowToken.Vault'
transaction(forTokenIdentifier: String) {
let tokenType: Type

prepare(signer: &Account) {
self.tokenType = CompositeType(forTokenIdentifier) ?? panic("Invalid Type \(forTokenIdentifier)")
}

execute {
MockOracle.bumpPrice(forToken: self.tokenType)
}
}
18 changes: 18 additions & 0 deletions cadence/transactions/mocks/oracle/set_price.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import "MockOracle"

/// Upserts the provided Token & price pairing in the MockOracle contract
///
/// @param vaultIdentifier: The Vault's Type identifier
/// e.g. vault.getType().identifier == 'A.0ae53cb6e3f42a79.FlowToken.Vault'
/// @param price: The price to set the token to in the MockOracle denominated in USD
transaction(forTokenIdentifier: String, price: UFix64) {
let tokenType: Type

prepare(signer: &Account) {
self.tokenType = CompositeType(forTokenIdentifier) ?? panic("Invalid Type \(forTokenIdentifier)")
}

execute {
MockOracle.setPrice(forToken: self.tokenType, price: price)
}
}
30 changes: 30 additions & 0 deletions cadence/transactions/usda/mint.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import "FungibleToken"

import "USDA"

/// MOCK TRANSACTION - DO NOT USE IN PRODUCTION
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
///
/// Mints the given amount of USDA tokens to the named recipient, reverting if they do not have a Vault configured
///
/// @param recipient: The Flow address to receive the minted tokens
/// @param amount: The amount of USDA tokens to mint
///
transaction(recipient: Address, amount: UFix64) {

let minter: &USDA.Minter
let receiver: &{FungibleToken.Receiver}

prepare(signer: auth(BorrowValue) &Account) {
self.minter = signer.storage.borrow<&USDA.Minter>(from: USDA.AdminStoragePath)
?? panic("Could not find USDA Minter at \(USDA.AdminStoragePath)")
self.receiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(USDA.ReceiverPublicPath)
?? panic("Could not find FungibleToken Receiver in \(recipient) at path \(USDA.ReceiverPublicPath)")
}

execute {
self.receiver.deposit(
from: <-self.minter.mintTokens(amount: amount)
)
}
}
Loading