Smart Contract
YieldToken
A.b1d63873c3cc9f79.YieldToken
1import FungibleToken from 0xf233dcee88fe0abe
2import MetadataViews from 0x1d7e57aa55817448
3import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
4
5///
6/// THIS CONTRACT IS A MOCK AND IS NOT INTENDED FOR USE IN PRODUCTION
7/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
8///
9access(all) contract YieldToken : FungibleToken {
10
11 /// Total supply of YieldToken in existence
12 access(all) var totalSupply: UFix64
13
14 /// Storage and Public Paths
15 access(all) let VaultStoragePath: StoragePath
16 access(all) let VaultPublicPath: PublicPath
17 access(all) let ReceiverPublicPath: PublicPath
18 access(all) let AdminStoragePath: StoragePath
19
20 /// The event that is emitted when new tokens are minted
21 access(all) event Minted(type: String, amount: UFix64, toUUID: UInt64, minterUUID: UInt64)
22 /// Emitted whenever a new Minter is created
23 access(all) event MinterCreated(uuid: UInt64)
24
25 /// createEmptyVault
26 ///
27 /// Function that creates a new Vault with a balance of zero
28 /// and returns it to the calling context. A user must call this function
29 /// and store the returned Vault in their storage in order to allow their
30 /// account to be able to receive deposits of this token type.
31 ///
32 access(all) fun createEmptyVault(vaultType: Type): @YieldToken.Vault {
33 return <- create Vault(balance: 0.0)
34 }
35
36 access(all) view fun getContractViews(resourceType: Type?): [Type] {
37 return [
38 Type<FungibleTokenMetadataViews.FTView>(),
39 Type<FungibleTokenMetadataViews.FTDisplay>(),
40 Type<FungibleTokenMetadataViews.FTVaultData>(),
41 Type<FungibleTokenMetadataViews.TotalSupply>()
42 ]
43 }
44
45 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
46 switch viewType {
47 case Type<FungibleTokenMetadataViews.FTView>():
48 return FungibleTokenMetadataViews.FTView(
49 ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
50 ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
51 )
52 case Type<FungibleTokenMetadataViews.FTDisplay>():
53 let media = MetadataViews.Media(
54 file: MetadataViews.HTTPFile(
55 url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
56 ),
57 mediaType: "image/svg+xml"
58 )
59 let medias = MetadataViews.Medias([media])
60 return FungibleTokenMetadataViews.FTDisplay(
61 name: "Mocked Yield Token",
62 symbol: "YIELD",
63 description: "A mocked token contract representing a receipt on some yield bearing vault",
64 externalURL: MetadataViews.ExternalURL("https://flow.com"),
65 logos: medias,
66 socials: {
67 "twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain")
68 }
69 )
70 case Type<FungibleTokenMetadataViews.FTVaultData>():
71 return FungibleTokenMetadataViews.FTVaultData(
72 storagePath: self.VaultStoragePath,
73 receiverPath: self.ReceiverPublicPath,
74 metadataPath: self.VaultPublicPath,
75 receiverLinkedType: Type<&YieldToken.Vault>(),
76 metadataLinkedType: Type<&YieldToken.Vault>(),
77 createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
78 return <-YieldToken.createEmptyVault(vaultType: Type<@YieldToken.Vault>())
79 })
80 )
81 case Type<FungibleTokenMetadataViews.TotalSupply>():
82 return FungibleTokenMetadataViews.TotalSupply(
83 totalSupply: YieldToken.totalSupply
84 )
85 }
86 return nil
87 }
88
89 /* --- CONSTRUCTS --- */
90
91 /// Vault
92 ///
93 /// Each user stores an instance of only the Vault in their storage
94 /// The functions in the Vault and governed by the pre and post conditions
95 /// in FungibleToken when they are called.
96 /// The checks happen at runtime whenever a function is called.
97 ///
98 /// Resources can only be created in the context of the contract that they
99 /// are defined in, so there is no way for a malicious user to create Vaults
100 /// out of thin air. A special Minter resource needs to be defined to mint
101 /// new tokens.
102 ///
103 access(all) resource Vault: FungibleToken.Vault {
104
105 /// The total balance of this vault
106 access(all) var balance: UFix64
107
108 /// Identifies the destruction of a Vault even when destroyed outside of Buner.burn() scope
109 access(all) event ResourceDestroyed(uuid: UInt64 = self.uuid, balance: UFix64 = self.balance)
110
111 init(balance: UFix64) {
112 self.balance = balance
113 }
114
115 /// Called when a fungible token is burned via the `Burner.burn()` method
116 access(contract) fun burnCallback() {
117 if self.balance > 0.0 {
118 YieldToken.totalSupply = YieldToken.totalSupply - self.balance
119 }
120 self.balance = 0.0
121 }
122
123 access(all) view fun getViews(): [Type] {
124 return YieldToken.getContractViews(resourceType: nil)
125 }
126
127 access(all) fun resolveView(_ view: Type): AnyStruct? {
128 return YieldToken.resolveContractView(resourceType: nil, viewType: view)
129 }
130
131 access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
132 let supportedTypes: {Type: Bool} = {}
133 supportedTypes[self.getType()] = true
134 return supportedTypes
135 }
136
137 access(all) view fun isSupportedVaultType(type: Type): Bool {
138 return self.getSupportedVaultTypes()[type] ?? false
139 }
140
141 access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
142 return amount <= self.balance
143 }
144
145 access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @YieldToken.Vault {
146 self.balance = self.balance - amount
147 return <-create Vault(balance: amount)
148 }
149
150 access(all) fun deposit(from: @{FungibleToken.Vault}) {
151 let vault <- from as! @YieldToken.Vault
152 let amount = vault.balance
153 vault.balance = 0.0
154 destroy vault
155
156 self.balance = self.balance + amount
157 }
158
159 access(all) fun createEmptyVault(): @YieldToken.Vault {
160 return <-create Vault(balance: 0.0)
161 }
162 }
163
164 /// Minter
165 ///
166 /// Resource object that token admin accounts can hold to mint new tokens.
167 ///
168 access(all) resource Minter {
169 /// Identifies when a Minter is destroyed, coupling with MinterCreated event to trace Minter UUIDs
170 access(all) event ResourceDestroyed(uuid: UInt64 = self.uuid)
171
172 init() {
173 emit MinterCreated(uuid: self.uuid)
174 }
175
176 /// mintTokens
177 ///
178 /// Function that mints new tokens, adds them to the total supply,
179 /// and returns them to the calling context.
180 ///
181 access(all) fun mintTokens(amount: UFix64): @YieldToken.Vault {
182 YieldToken.totalSupply = YieldToken.totalSupply + amount
183 let vault <-create Vault(balance: amount)
184 emit Minted(type: vault.getType().identifier, amount: amount, toUUID: vault.uuid, minterUUID: self.uuid)
185 return <-vault
186 }
187 }
188
189 init(initialMint: UFix64) {
190
191 self.totalSupply = 0.0
192
193 let address = self.account.address
194 self.VaultStoragePath = StoragePath(identifier: "yieldTokenVault_\(address)")!
195 self.VaultPublicPath = PublicPath(identifier: "yieldTokenVault_\(address)")!
196 self.ReceiverPublicPath = PublicPath(identifier: "yieldTokenReceiver_\(address)")!
197 self.AdminStoragePath = StoragePath(identifier: "yieldTokenAdmin_\(address)")!
198
199
200 // Create a public capability to the stored Vault that exposes
201 // the `deposit` method and getAcceptedTypes method through the `Receiver` interface
202 // and the `balance` method through the `Balance` interface
203 //
204 self.account.storage.save(<-create Vault(balance: self.totalSupply), to: self.VaultStoragePath)
205 let vaultCap = self.account.capabilities.storage.issue<&YieldToken.Vault>(self.VaultStoragePath)
206 self.account.capabilities.publish(vaultCap, at: self.VaultPublicPath)
207 let receiverCap = self.account.capabilities.storage.issue<&YieldToken.Vault>(self.VaultStoragePath)
208 self.account.capabilities.publish(receiverCap, at: self.ReceiverPublicPath)
209
210 // Create a Minter & mint the initial supply of tokens to the contract account's Vault
211 let admin <- create Minter()
212
213 self.account.capabilities.borrow<&Vault>(self.ReceiverPublicPath)!.deposit(
214 from: <- admin.mintTokens(amount: initialMint)
215 )
216
217 self.account.storage.save(<-admin, to: self.AdminStoragePath)
218 }
219}
220