Smart Contract
OzoneToken
A.256599e1b091be12.OzoneToken
1//SPDX-License-Identifier : CC-BY-NC-4.0
2
3import FungibleToken from 0xf233dcee88fe0abe
4import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
5import MetadataViews from 0x1d7e57aa55817448
6
7access(all) contract OzoneToken: FungibleToken {
8
9 // Event that is emitted when the contract is created
10 access(all) event TokensInitialized(initialSupply: UFix64)
11
12 // Event that is emitted when tokens are withdrawn from a Vault
13 access(all) event TokensWithdrawn(amount: UFix64, from: Address?)
14
15 // Event that is emitted when tokens are deposited to a Vault
16 access(all) event TokensDeposited(amount: UFix64, to: Address?)
17
18 // Event that is emitted when new tokens are minted
19 access(all) event TokensMinted(amount: UFix64)
20
21 // Event that is emitted when tokens are destroyed
22 access(all) event TokensBurned(amount: UFix64)
23
24 // Event that is emitted when a new burner resource is created
25 access(all) event BurnerCreated()
26
27 // The storage path for the admin resource
28 access(all) let AdminStoragePath: StoragePath
29
30 // The storage path for ozone token vault
31 access(all) let OzoneTokenVaultPath: StoragePath
32
33 // The public path for ozone token receiver
34 access(all) let OzoneTokenReceiverPath: PublicPath
35
36 // The public path for ozone token balance
37 access(all) let OzoneTokenBalancePath: PublicPath
38
39 // The storage Path for minters' MinterProxy
40 access(all) let MinterProxyStoragePath: StoragePath
41
42 // The public path for minters' MinterProxy capability
43 access(all) let MinterProxyPublicPath: PublicPath
44
45 // Event that is emitted when a new minter resource is created
46 access(all) event MinterCreated()
47
48 // Total supply of ozonetoken in existence
49 access(all) var totalSupply: UFix64
50
51 /// Function that resolves a metadata view for this contract.
52 ///
53 /// @param view: The Type of the desired view.
54 /// @return A structure representing the requested view.
55 ///
56 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
57 switch viewType {
58 case Type<FungibleTokenMetadataViews.FTView>():
59 return FungibleTokenMetadataViews.FTView(
60 ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
61 ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
62 )
63 case Type<FungibleTokenMetadataViews.FTDisplay>():
64 let media = MetadataViews.Media(
65 file: MetadataViews.HTTPFile(
66 url: ""
67 ),
68 mediaType: ""
69 )
70 let medias = MetadataViews.Medias([media])
71 return FungibleTokenMetadataViews.FTDisplay(
72 name: "Ozone Metaverse Token",
73 symbol: "OZONE",
74 description: "Ozone is the enterprise grade platform for virtual worlds building. Simple to use - Powerful - 100% browser based.",
75 externalURL: MetadataViews.ExternalURL("https://ozonemetaverse.io"),
76 logos: medias,
77 socials: {
78 "twitter": MetadataViews.ExternalURL("https://twitter.com/ozonemetaverse"),
79 "discord": MetadataViews.ExternalURL("https://discord.gg/ozonemetaverse")
80 }
81 )
82 case Type<FungibleTokenMetadataViews.FTVaultData>():
83 return FungibleTokenMetadataViews.FTVaultData(
84 storagePath: self.OzoneTokenVaultPath,
85 receiverPath: self.OzoneTokenReceiverPath,
86 metadataPath: self.OzoneTokenBalancePath,
87 receiverLinkedType: Type<&OzoneToken.Vault>(),
88 metadataLinkedType: Type<&OzoneToken.Vault>(),
89 createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
90 return <-OzoneToken.createEmptyVault(vaultType: Type<@OzoneToken.Vault>())
91 })
92 )
93 case Type<FungibleTokenMetadataViews.TotalSupply>():
94 return FungibleTokenMetadataViews.TotalSupply(
95 totalSupply: OzoneToken.totalSupply
96 )
97 }
98 return nil
99 }
100
101 /// Function that returns all the Metadata Views implemented by a Non Fungible Token
102 ///
103 /// @return An array of Types defining the implemented views. This value will be used by
104 /// developers to know which parameter to pass to the resolveView() method.
105 ///
106 access(all) view fun getContractViews(resourceType: Type?): [Type] {
107 return [
108 Type<FungibleTokenMetadataViews.FTView>(),
109 Type<FungibleTokenMetadataViews.FTDisplay>(),
110 Type<FungibleTokenMetadataViews.FTVaultData>(),
111 Type<FungibleTokenMetadataViews.TotalSupply>()
112 ]
113 }
114
115 // Vault
116 //
117 // Each user stores an instance of only the Vault in their storage
118 // The functions in the Vault are governed by the pre and post conditions
119 // in FungibleToken when they are called.
120 // The checks happen at runtime whenever a function is called.
121 //
122 // Resources can only be created in the context of the contract that they
123 // are defined in, so there is no way for a malicious user to create Vaults
124 // out of thin air. A special Minter resource needs to be defined to mint
125 // new tokens.
126 //
127 access(all) resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance, FungibleToken.Vault {
128
129 // holds the balance of a users tokens
130 access(all) var balance: UFix64
131
132 // initialize the balance at resource creation time
133 init(balance: UFix64) {
134 self.balance = balance
135 }
136
137 /// Called when a fungible token is burned via the `Burner.burn()` method
138 access(contract) fun burnCallback() {
139 if self.balance > 0.0 {
140 OzoneToken.totalSupply = OzoneToken.totalSupply - self.balance
141 }
142 self.balance = 0.0
143 }
144
145 // withdraw
146 //
147 // Function that takes an integer amount as an argument
148 // and withdraws that amount from the Vault.
149 // It creates a new temporary Vault that is used to hold
150 // the money that is being transferred. It returns the newly
151 // created Vault to the context that called so it can be deposited
152 // elsewhere.
153 //
154 access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
155 self.balance = self.balance - amount
156 emit TokensWithdrawn(amount: amount, from: self.owner?.address)
157 return <-create Vault(balance: amount)
158 }
159
160 access(all) view fun getViews(): [Type] {
161 return OzoneToken.getContractViews(resourceType: nil)
162 }
163
164 access(all) fun resolveView(_ view: Type): AnyStruct? {
165 return OzoneToken.resolveContractView(resourceType: nil, viewType: view)
166 }
167
168 /// Asks if the amount can be withdrawn from this vault
169 access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
170 return amount <= self.balance
171 }
172
173 access(all) fun createEmptyVault(): @OzoneToken.Vault {
174 return <-create Vault(balance: 0.0)
175 }
176
177 /// Returns whether the specified type can be deposited
178 access(all) view fun isSupportedVaultType(type: Type): Bool {
179 return self.getSupportedVaultTypes()[type] ?? false
180 }
181
182 // deposit
183 //
184 // Function that takes a Vault object as an argument and adds
185 // its balance to the balance of the owners Vault.
186 // It is allowed to destroy the sent Vault because the Vault
187 // was a temporary holder of the tokens. The Vault's balance has
188 // been consumed and therefore can be destroyed.
189 access(all) fun deposit(from: @{FungibleToken.Vault}) {
190 let vault <- from as! @OzoneToken.Vault
191 self.balance = self.balance + vault.balance
192 emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
193 vault.balance = 0.0
194 destroy vault
195 }
196 }
197
198 // createEmptyVault
199 //
200 // Function that creates a new Vault with a balance of zero
201 // and returns it to the calling context. A user must call this function
202 // and store the returned Vault in their storage in order to allow their
203 // account to be able to receive deposits of this token type.
204 //
205 access(all) fun createEmptyVault(vaultType: Type): @OzoneToken.Vault {
206 return <-create Vault(balance: 0.0)
207 }
208
209 // Minter
210 //
211 // Resource object that can mint new tokens.
212 // The admin stores this and passes it to the minter account as a capability wrapper resource.
213 //
214 access(all) resource Minter {
215
216 // mintTokens
217 //
218 // Function that mints new tokens, adds them to the total supply,
219 // and returns them to the calling context.
220 //
221 access(all) fun mintTokens(amount: UFix64): @OzoneToken.Vault {
222 pre {
223 amount > 0.0: "Amount minted must be greater than zero"
224 }
225 OzoneToken.totalSupply = OzoneToken.totalSupply + amount
226 emit TokensMinted(amount: amount)
227 return <-create Vault(balance: amount)
228 }
229
230 }
231
232 // Burner
233 //
234 // Resource object that token admin accounts can hold to burn tokens.
235 //
236 access(all) resource Burner {
237
238 // burnTokens
239 //
240 // Function that destroys a Vault instance, effectively burning the tokens.
241 //
242 // Note: the burned tokens are automatically subtracted from the
243 // total supply in the Vault destructor.
244 //
245 access(all) fun burnTokens(from: @OzoneToken.Vault) {
246 let vault <- from
247 let amount = vault.balance
248 destroy vault
249 emit TokensBurned(amount: amount)
250 }
251 }
252
253 access(all) resource interface MinterProxyPublic {
254 access(all) fun setMinterCapability(cap: Capability<&Minter>)
255 }
256
257 // MinterProxy
258 //
259 // Resource object holding a capability that can be used to mint new tokens.
260 // The resource that this capability represents can be deleted by the admin
261 // in order to unilaterally revoke minting capability if needed.
262
263 access(all) resource MinterProxy: MinterProxyPublic {
264
265 // access(self) so nobody else can copy the capability and use it.
266 access(self) var minterCapability: Capability<&Minter>?
267
268 // Anyone can call this, but only the admin can create Minter capabilities,
269 // so the type system constrains this to being called by the admin.
270 access(all) fun setMinterCapability(cap: Capability<&Minter>) {
271 self.minterCapability = cap
272 }
273
274 access(all) fun mintTokens(amount: UFix64): @OzoneToken.Vault {
275 return <- self.minterCapability!
276 .borrow()!
277 .mintTokens(amount:amount)
278 }
279
280 init() {
281 self.minterCapability = nil
282 }
283
284 }
285
286 // createMinterProxy
287 //
288 // Function that creates a MinterProxy.
289 // Anyone can call this, but the MinterProxy cannot mint without a Minter capability,
290 // and only the admin can provide that.
291 //
292 access(all) fun createMinterProxy(): @MinterProxy {
293 return <- create MinterProxy()
294 }
295
296 // Administrator
297 //
298 // A resource that allows new minters to be created
299 //
300 // We will only want one minter for now, but might need to add or replace them in future.
301 // The Minter/Minter Proxy structure enables this.
302 // Ideally we would create this structure in a single function, generate the paths from the address
303 // and cache all of this information to enable easy revocation but String/Path comversion isn't yet supported.
304 //
305 access(all) resource Administrator {
306
307 // createNewMinter
308 //
309 // Function that creates a Minter resource.
310 // This should be stored at a unique path in storage then a capability to it wrapped
311 // in a MinterProxy to be stored in a minter account's storage.
312 // This is done by the minter account running:
313 // transactions/ozonetoken/minter/setup_minter_account.cdc
314 // then the admin account running:
315 // transactions/ozonetoken/admin/deposit_minter_capability.cdc
316 //
317 access(all) fun createNewMinter(): @Minter {
318 emit MinterCreated()
319 return <- create Minter()
320 }
321
322 // createNewBurner
323 //
324 // Function that creates and returns a new burner resource
325 //
326 access(all) fun createNewBurner(): @Burner {
327 emit BurnerCreated()
328 return <-create Burner()
329 }
330 }
331
332 init() {
333 self.AdminStoragePath = /storage/OzonetokenAdmin
334 self.MinterProxyPublicPath = /public/OzonetokenMinterProxy
335 self.MinterProxyStoragePath = /storage/OzonetokenMinterProxy
336
337 self.OzoneTokenVaultPath = /storage/OzonetokenVault
338 self.OzoneTokenReceiverPath = /public/OzonetokenReceiver
339 self.OzoneTokenBalancePath = /public/OzonetokenBalance
340
341 self.totalSupply = 0.0
342
343 let admin <- create Administrator()
344
345 // Emit an event that shows that the contract was initialized
346 emit TokensInitialized(initialSupply: 0.0)
347
348 let minter <- admin.createNewMinter()
349
350 let mintedVault <- minter.mintTokens(amount: 1000000.0)
351
352 destroy minter
353
354 self.account.storage.save(<-admin, to: self.AdminStoragePath)
355 self.account.storage.save(<-mintedVault, to: self.OzoneTokenVaultPath)
356
357 let capRes = self.account.capabilities.storage.issue<&OzoneToken.Vault>(self.OzoneTokenVaultPath)
358 self.account.capabilities.publish(capRes, at: self.OzoneTokenReceiverPath)
359
360 let capBalance = self.account.capabilities.storage.issue<&OzoneToken.Vault>(self.OzoneTokenVaultPath)
361 self.account.capabilities.publish(capBalance, at: self.OzoneTokenBalancePath)
362 }
363}
364