Smart Contract
MoxyVaultToken
A.123cb47fe122f6e3.MoxyVaultToken
1import FungibleToken from 0xf233dcee88fe0abe
2import MoxyData from 0x123cb47fe122f6e3
3
4
5pub contract MoxyVaultToken: FungibleToken {
6
7 /// Total supply of MoxyVaultTokens in existence
8 pub var totalSupply: UFix64
9 access(contract) var totalSupplies: @MoxyData.OrderedDictionary
10 pub var numberOfHolders: UInt
11
12 /// TokensInitialized
13 ///
14 /// The event that is emitted when the contract is created
15 pub event TokensInitialized(initialSupply: UFix64)
16
17 /// TokensWithdrawn
18 ///
19 /// The event that is emitted when tokens are withdrawn from a Vault
20 pub event TokensWithdrawn(amount: UFix64, from: Address?)
21
22 /// TokensDeposited
23 ///
24 /// The event that is emitted when tokens are deposited to a Vault
25 pub event TokensDeposited(amount: UFix64, to: Address?)
26
27 /// TokensMinted
28 ///
29 /// The event that is emitted when new tokens are minted
30 pub event TokensMinted(amount: UFix64)
31
32 /// TokensBurned
33 ///
34 /// The event that is emitted when tokens are destroyed
35 pub event TokensBurned(amount: UFix64)
36
37 /// MinterCreated
38 ///
39 /// The event that is emitted when a new minter resource is created
40 pub event MinterCreated(allowedAmount: UFix64)
41
42 /// BurnerCreated
43 ///
44 /// The event that is emitted when a new burner resource is created
45 pub event BurnerCreated()
46
47 pub event MVToMOXYConverterCreated(conversionAmount: UFix64, timestamp: UFix64)
48
49 /// Vault
50 ///
51 /// Each user stores an instance of only the Vault in their storage
52 /// The functions in the Vault and governed by the pre and post conditions
53 /// in FungibleToken when they are called.
54 /// The checks happen at runtime whenever a function is called.
55 ///
56 /// Resources can only be created in the context of the contract that they
57 /// are defined in, so there is no way for a malicious user to create Vaults
58 /// out of thin air. A special Minter resource needs to be defined to mint
59 /// new tokens.
60 ///
61 pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance, DailyBalancesInterface, ReceiverInterface {
62
63 /// The total balance of this vault
64 pub var balance: UFix64
65 access(contract) var dailyBalances: @MoxyData.OrderedDictionary
66
67 // initialize the balance at resource creation time
68 init(balance: UFix64) {
69 self.balance = balance
70 self.dailyBalances <- MoxyData.createNewOrderedDictionary()
71 if (balance > 0.0) {
72 self.dailyBalances.setAmountFor(timestamp: getCurrentBlock().timestamp, amount: balance)
73 }
74 }
75
76 pub fun getDailyBalanceFor(timestamp: UFix64): UFix64? {
77 return self.dailyBalances.getValueOrMostRecentFor(timestamp: timestamp)
78 }
79
80 pub fun getDailyBalancesChangesUpTo(timestamp: UFix64): {UFix64:UFix64} {
81 return self.dailyBalances.getValueChangesUpTo(timestamp: timestamp)
82 }
83
84 pub fun getDailyBalanceChange(timestamp: UFix64): Fix64 {
85 return self.dailyBalances.getValueChange(timestamp: timestamp)
86 }
87
88 pub fun getLastTimestampAdded(): UFix64? {
89 return self.dailyBalances.getLastKeyAdded()
90 }
91
92 pub fun getFirstTimestampAdded(): UFix64? {
93 return self.dailyBalances.getFirstKeyAdded()
94 }
95
96 /// withdraw
97 ///
98 /// Function that takes an amount as an argument
99 /// and withdraws that amount from the Vault.
100 ///
101 /// It creates a new temporary Vault that is used to hold
102 /// the money that is being transferred. It returns the newly
103 /// created Vault to the context that called so it can be deposited
104 /// elsewhere.
105 ///
106 pub fun withdraw(amount: UFix64): @FungibleToken.Vault {
107 panic("MV token can't be withdrawn")
108 }
109
110 access(account) fun withdrawAmount(amount: UFix64): @FungibleToken.Vault {
111 let vault <- self.vaultToConvert(amount: amount)
112 return <- vault
113 }
114
115 /// deposit
116 ///
117 /// Function that takes a Vault object as an argument and adds
118 /// its balance to the balance of the owners Vault.
119 ///
120 /// It is allowed to destroy the sent Vault because the Vault
121 /// was a temporary holder of the tokens. The Vault's balance has
122 /// been consumed and therefore can be destroyed.
123 ///
124
125 pub fun deposit(from: @FungibleToken.Vault) {
126 panic("MV tokens can't be directly deposited.")
127 }
128
129 // Deposit keeping original daily balances that cames from the vault
130 access(account) fun depositAmount(from: @FungibleToken.Vault) {
131 let vault <- from as! @MoxyVaultToken.Vault
132
133 if (self.owner != nil && self.balance == 0.0 && vault.balance > 0.0) {
134 MoxyVaultToken.numberOfHolders = MoxyVaultToken.numberOfHolders + 1
135 }
136
137 let dailyBalances = vault.dailyBalances.getDictionary()
138
139 for time in dailyBalances.keys {
140 self.dailyBalances.setAmountFor(timestamp: time, amount: dailyBalances[time]!)
141 }
142
143 self.balance = self.balance + vault.balance
144
145 emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
146
147 vault.dailyBalances.withdrawValueFromOldest(amount: vault.balance)
148 vault.balance = 0.0
149
150 destroy vault
151 }
152
153
154 access(account) fun depositDueConversion(from: @FungibleToken.Vault) {
155 let timestamp = getCurrentBlock().timestamp
156 return self.depositFor(from: <-from, timestamp: timestamp)
157 }
158
159 access(contract) fun depositFor(from: @FungibleToken.Vault, timestamp: UFix64) {
160 let vault <- from as! @MoxyVaultToken.Vault
161
162 if (self.owner != nil && self.balance == 0.0 && vault.balance > 0.0) {
163 MoxyVaultToken.numberOfHolders = MoxyVaultToken.numberOfHolders + 1
164 }
165
166 self.dailyBalances.setAmountFor(timestamp: timestamp, amount: vault.balance)
167
168 self.balance = self.balance + vault.balance
169
170 emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
171
172 vault.dailyBalances.withdrawValueFromOldest(amount: vault.balance)
173 vault.balance = 0.0
174
175 destroy vault
176 }
177
178 access(contract) fun depositWithAges(balance: UFix64, ages: {UFix64:UFix64}) {
179 post {
180 total == balance : "Cannot assigning ages, please check amounts."
181 }
182
183 if (self.owner != nil && self.balance == 0.0 && balance > 0.0) {
184 MoxyVaultToken.numberOfHolders = MoxyVaultToken.numberOfHolders + 1
185 }
186
187 var total = 0.0
188 for time in ages.keys {
189 self.dailyBalances.setAmountFor(timestamp: time, amount: ages[time]!)
190 total = total + ages[time]!
191 }
192
193 self.balance = self.balance + balance
194 }
195
196 pub fun createNewMVConverter(privateVaultRef: Capability<&MoxyVaultToken.Vault>, allowedAmount: UFix64): @MVConverter {
197 return <- create MVConverter(privateVaultRef: privateVaultRef, allowedAmount: allowedAmount, address: self.owner!.address)
198 }
199
200 access(contract) fun vaultToConvert(amount: UFix64): @FungibleToken.Vault {
201 // Withdraw can only be done when a conversion MV to MOX is requested
202 // withdraw are done from oldest deposits to newer deposits
203 let balanceBefore = self.balance
204
205 let dict = self.dailyBalances.withdrawValueFromOldest(amount: amount)
206 self.balance = self.balance - amount
207
208 if (self.balance == 0.0 && balanceBefore > 0.0 && self.owner != nil) {
209 MoxyVaultToken.numberOfHolders = MoxyVaultToken.numberOfHolders - 1
210 }
211
212 emit TokensWithdrawn(amount: amount, from: self.owner?.address)
213 let vault <- MoxyVaultToken.createEmptyVault()
214
215 vault.depositWithAges(balance: amount, ages: dict)
216
217 return <- vault
218 }
219
220
221 destroy() {
222 destroy self.dailyBalances
223 MoxyVaultToken.totalSupply = MoxyVaultToken.totalSupply - self.balance
224 }
225 }
226
227 /// createEmptyVault
228 ///
229 /// Function that creates a new Vault with a balance of zero
230 /// and returns it to the calling context. A user must call this function
231 /// and store the returned Vault in their storage in order to allow their
232 /// account to be able to receive deposits of this token type.
233 ///
234 pub fun createEmptyVault(): @Vault {
235 return <-create Vault(balance: 0.0)
236 }
237
238 pub resource Administrator {
239
240 /// createNewMinter
241 ///
242 /// Function that creates and returns a new minter resource
243 ///
244 access(account) fun createNewMinter(allowedAmount: UFix64): @Minter {
245 emit MinterCreated(allowedAmount: allowedAmount)
246 return <-create Minter(allowedAmount: allowedAmount)
247 }
248
249 /// createNewBurner
250 ///
251 /// Function that creates and returns a new burner resource
252 ///
253 access(account) fun createNewBurner(): @Burner {
254 emit BurnerCreated()
255 return <-create Burner()
256 }
257 }
258
259 /// Minter
260 ///
261 /// Resource object that token admin accounts can hold to mint new tokens.
262 ///
263 pub resource Minter {
264
265 /// The amount of tokens that the minter is allowed to mint
266 pub var allowedAmount: UFix64
267
268 /// mintTokens
269 ///
270 /// Function that mints new tokens, adds them to the total supply,
271 /// and returns them to the calling context.
272 ///
273 pub fun mintTokens(amount: UFix64): @MoxyVaultToken.Vault {
274 let timestamp = getCurrentBlock().timestamp
275 return <-self.mintTokensFor(amount: amount, timestamp: timestamp)
276 }
277
278 pub fun mintTokensFor(amount: UFix64, timestamp: UFix64): @MoxyVaultToken.Vault {
279 pre {
280 amount > 0.0: "Amount minted must be greater than zero"
281 amount <= self.allowedAmount: "Amount minted must be less than the allowed amount"
282 }
283
284 if (!MoxyVaultToken.totalSupplies.canUpdateTo(timestamp: timestamp)) {
285 panic("Cannot mint MV token for events before the last registerd")
286 }
287
288 MoxyVaultToken.totalSupplies.setAmountFor(timestamp: timestamp, amount: amount)
289
290 MoxyVaultToken.totalSupply = MoxyVaultToken.totalSupply + amount
291
292 self.allowedAmount = self.allowedAmount - amount
293
294 emit TokensMinted(amount: amount)
295 let vault <-create Vault(balance: amount)
296
297 return <- vault
298 }
299
300 init(allowedAmount: UFix64) {
301 self.allowedAmount = allowedAmount
302 }
303 }
304
305 /// Burner
306 ///
307 /// Resource object that token admin accounts can hold to burn tokens.
308 ///
309 pub resource Burner {
310
311 /// burnTokens
312 ///
313 /// Function that destroys a Vault instance, effectively burning the tokens.
314 ///
315 /// Note: the burned tokens are automatically subtracted from the
316 /// total supply in the Vault destructor.
317 ///
318 pub fun burnTokens(from: @FungibleToken.Vault) {
319 let vault <- from as! @MoxyVaultToken.Vault
320 let amount = vault.balance
321 destroy vault
322 emit TokensBurned(amount: amount)
323 }
324 }
325
326 pub resource MVConverter: Converter {
327 pub var privateVaultRef: Capability<&MoxyVaultToken.Vault>
328 pub var allowedAmount: UFix64
329 pub var address: Address
330
331 pub fun getDailyVault(amount: UFix64): @FungibleToken.Vault {
332 pre {
333 amount > 0.0: "Amount to burn must be greater than zero"
334 amount <= self.allowedAmount: "Amount to burn must be equal or less than the allowed amount. Allowed amount: ".concat(self.allowedAmount.toString()).concat(" amount: ").concat(amount.toString())
335 }
336 self.allowedAmount = self.allowedAmount - amount
337 let vault <- self.privateVaultRef.borrow()!.vaultToConvert(amount: amount) as! @MoxyVaultToken.Vault
338
339 return <-vault
340 }
341
342 init(privateVaultRef: Capability<&MoxyVaultToken.Vault>, allowedAmount: UFix64, address: Address ) {
343 self.privateVaultRef = privateVaultRef
344 self.allowedAmount = allowedAmount
345 self.address = address
346 }
347 }
348
349 pub resource interface DailyBalancesInterface {
350 pub fun getDailyBalanceFor(timestamp: UFix64): UFix64?
351 pub fun getDailyBalanceChange(timestamp: UFix64): Fix64
352 pub fun getLastTimestampAdded(): UFix64?
353 pub fun getFirstTimestampAdded(): UFix64?
354 pub fun getDailyBalancesChangesUpTo(timestamp: UFix64): {UFix64:UFix64}
355 }
356
357 pub resource interface ReceiverInterface {
358 access(account) fun depositDueConversion(from: @FungibleToken.Vault)
359 access(account) fun depositAmount(from: @FungibleToken.Vault)
360 }
361
362 pub resource interface Converter {
363 pub fun getDailyVault(amount: UFix64): @FungibleToken.Vault
364 }
365
366 pub fun getLastTotalSupplyTimestampAdded(): UFix64? {
367 return self.totalSupplies.getLastKeyAdded()
368 }
369
370 pub fun getTotalSupplyFor(timestamp: UFix64): UFix64 {
371 return self.totalSupplies.getValueOrMostRecentFor(timestamp: timestamp)
372 }
373
374 pub fun getDailyChangeTo(timestamp: UFix64): Fix64 {
375 return self.totalSupplies.getValueChange(timestamp: timestamp)
376 }
377
378 access(contract) fun destroyTotalSupply(orderedDictionary: @MoxyData.OrderedDictionary) {
379 self.totalSupplies.destroyWith(orderedDictionary: <-orderedDictionary)
380 }
381
382 pub let moxyVaultTokenVaultStorage: StoragePath
383 pub let moxyVaultTokenVaultPrivate: PrivatePath
384 pub let moxyVaultTokenAdminStorage: StoragePath
385 pub let moxyVaultTokenReceiverPath: PublicPath
386 pub let moxyVaultTokenBalancePath: PublicPath
387 pub let moxyVaultTokenDailyBalancePath: PublicPath
388 pub let moxyVaultTokenReceiverTimestampPath: PublicPath
389 // Paths for Locked tonkens
390 pub let moxyVaultTokenLockedVaultStorage: StoragePath
391 pub let moxyVaultTokenLockedVaultPrivate: PrivatePath
392 pub let moxyVaultTokenLockedBalancePath: PublicPath
393 pub let moxyVaultTokenLockedReceiverPath: PublicPath
394
395 init() {
396 self.totalSupply = 0.0
397 self.totalSupplies <- MoxyData.createNewOrderedDictionary()
398 self.numberOfHolders = 0
399
400 self.moxyVaultTokenVaultStorage = /storage/moxyVaultTokenVault
401 self.moxyVaultTokenVaultPrivate = /private/moxyVaultTokenVault
402 self.moxyVaultTokenAdminStorage = /storage/moxyVaultTokenAdmin
403 self.moxyVaultTokenReceiverPath = /public/moxyVaultTokenReceiver
404 self.moxyVaultTokenBalancePath = /public/moxyVaultTokenBalance
405 self.moxyVaultTokenDailyBalancePath = /public/moxyVaultTokenDailyBalance
406 self.moxyVaultTokenReceiverTimestampPath = /public/moxyVaultTokenReceiverTimestamp
407 // Locked vaults
408 self.moxyVaultTokenLockedVaultStorage = /storage/moxyVaultTokenLockedVault
409 self.moxyVaultTokenLockedVaultPrivate = /private/moxyVaultTokenLockedVault
410 self.moxyVaultTokenLockedBalancePath = /public/moxyVaultTokenLockedBalance
411 self.moxyVaultTokenLockedReceiverPath = /public/moxyVaultTokenLockedReceiver
412
413 // Create the Vault with the total supply of tokens and save it in storage
414 //
415 let vault <- create Vault(balance: self.totalSupply)
416 self.account.save(<-vault, to: self.moxyVaultTokenVaultStorage)
417
418 // Private access to MoxyVault token Vault
419 self.account.link<&MoxyVaultToken.Vault>(
420 self.moxyVaultTokenVaultPrivate,
421 target: self.moxyVaultTokenVaultStorage
422 )
423
424 // Create a public capability to the stored Vault that only exposes
425 // the `deposit` method through the `Receiver` interface
426 //
427 self.account.link<&{FungibleToken.Receiver}>(
428 self.moxyVaultTokenReceiverPath,
429 target: self.moxyVaultTokenVaultStorage
430 )
431 // Link to receive tokens in a specific timestamp
432 self.account.link<&{MoxyVaultToken.ReceiverInterface}>(
433 self.moxyVaultTokenReceiverTimestampPath,
434 target: self.moxyVaultTokenVaultStorage
435 )
436
437 // Create a public capability to the stored Vault that only exposes
438 // the `balance` field through the `Balance` interface
439 //
440 self.account.link<&MoxyVaultToken.Vault{FungibleToken.Balance}>(
441 self.moxyVaultTokenBalancePath,
442 target: self.moxyVaultTokenVaultStorage
443 )
444 self.account.link<&MoxyVaultToken.Vault{DailyBalancesInterface}>(
445 self.moxyVaultTokenDailyBalancePath,
446 target: self.moxyVaultTokenVaultStorage
447 )
448
449 let admin <- create Administrator()
450 self.account.save(<-admin, to: self.moxyVaultTokenAdminStorage)
451
452 // Emit an event that shows that the contract was initialized
453 //
454 emit TokensInitialized(initialSupply: self.totalSupply)
455 }
456}
457
458