Smart Contract
PlayToken
A.123cb47fe122f6e3.PlayToken
1import FungibleToken from 0xf233dcee88fe0abe
2import MoxyData from 0x123cb47fe122f6e3
3
4
5pub contract PlayToken: FungibleToken {
6
7 /// Total supply of PlayTokens in existence
8 pub var totalSupply: UFix64
9 access(contract) var totalSupplies: @MoxyData.OrderedDictionary
10
11 /// TokensInitialized
12 ///
13 /// The event that is emitted when the contract is created
14 pub event TokensInitialized(initialSupply: UFix64)
15
16 /// TokensWithdrawn
17 ///
18 /// The event that is emitted when tokens are withdrawn from a Vault
19 pub event TokensWithdrawn(amount: UFix64, from: Address?)
20
21 /// TokensDeposited
22 ///
23 /// The event that is emitted when tokens are deposited to a Vault
24 pub event TokensDeposited(amount: UFix64, to: Address?)
25
26 /// TokensMinted
27 ///
28 /// The event that is emitted when new tokens are minted
29 pub event TokensMinted(amount: UFix64)
30
31 /// TokensBurned
32 ///
33 /// The event that is emitted when tokens are destroyed
34 pub event TokensBurned(amount: UFix64)
35
36 /// MinterCreated
37 ///
38 /// The event that is emitted when a new minter resource is created
39 pub event MinterCreated(allowedAmount: UFix64)
40
41 /// BurnerCreated
42 ///
43 /// The event that is emitted when a new burner resource is created
44 pub event BurnerCreated()
45
46 /// Vault
47 ///
48 /// Each user stores an instance of only the Vault in their storage
49 /// The functions in the Vault and governed by the pre and post conditions
50 /// in FungibleToken when they are called.
51 /// The checks happen at runtime whenever a function is called.
52 ///
53 /// Resources can only be created in the context of the contract that they
54 /// are defined in, so there is no way for a malicious user to create Vaults
55 /// out of thin air. A special Minter resource needs to be defined to mint
56 /// new tokens.
57 ///
58 pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance, DailyBalancesInterface, ReceiverInterface {
59
60 /// The total balance of this vault
61 pub var balance: UFix64
62 access(contract) var dailyBalances: @MoxyData.OrderedDictionary
63
64 // initialize the balance at resource creation time
65 init(balance: UFix64) {
66 self.balance = balance
67 self.dailyBalances <- MoxyData.createNewOrderedDictionary()
68 }
69
70 pub fun getDailyBalanceFor(timestamp: UFix64): UFix64? {
71 // Returns the balance for the requested day or zero
72 // if no records at that day.
73 return self.dailyBalances.getValueOrMostRecentFor(timestamp: timestamp)
74 }
75
76 pub fun getBalanceFor(timestamp: UFix64): UFix64? {
77 return self.dailyBalances.getValueFor(timestamp: timestamp)
78 }
79
80 /// withdraw
81 ///
82 /// Function that takes an amount as an argument
83 /// and withdraws that amount from the Vault.
84 ///
85 /// It creates a new temporary Vault that is used to hold
86 /// the money that is being transferred. It returns the newly
87 /// created Vault to the context that called so it can be deposited
88 /// elsewhere.
89 ///
90 pub fun withdraw(amount: UFix64): @FungibleToken.Vault {
91 self.balance = self.balance - amount
92 emit TokensWithdrawn(amount: amount, from: self.owner?.address)
93 return <-create Vault(balance: amount)
94 }
95
96 /// deposit
97 ///
98 /// Function that takes a Vault object as an argument and adds
99 /// its balance to the balance of the owners Vault.
100 ///
101 /// It is allowed to destroy the sent Vault because the Vault
102 /// was a temporary holder of the tokens. The Vault's balance has
103 /// been consumed and therefore can be destroyed.
104 ///
105 pub fun deposit(from: @FungibleToken.Vault) {
106 // PLAY Tokens can't be transferred
107 panic("PLAY can't be deposited")
108 }
109
110 pub fun convertedFromMOXY(from: @FungibleToken.Vault) {
111 let vault <- from as! @PlayToken.Vault
112
113 self.dailyBalances.setAmountFor(timestamp: getCurrentBlock().timestamp, amount: vault.balance)
114
115 self.balance = self.balance + vault.balance
116
117 emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
118 vault.balance = 0.0
119 destroy vault
120 }
121
122 destroy() {
123 // Updating total supply registered daily
124 PlayToken.destroyTotalSupply(orderedDictionary: <-self.dailyBalances)
125 PlayToken.totalSupply = PlayToken.totalSupply - self.balance
126 }
127 }
128
129 /// createEmptyVault
130 ///
131 /// Function that creates a new Vault with a balance of zero
132 /// and returns it to the calling context. A user must call this function
133 /// and store the returned Vault in their storage in order to allow their
134 /// account to be able to receive deposits of this token type.
135 ///
136 pub fun createEmptyVault(): @Vault {
137 return <-create Vault(balance: 0.0)
138 }
139
140 pub resource Administrator {
141
142 /// createNewMinter
143 ///
144 /// Function that creates and returns a new minter resource
145 ///
146 pub fun createNewMinter(allowedAmount: UFix64): @Minter {
147 emit MinterCreated(allowedAmount: allowedAmount)
148 return <-create Minter(allowedAmount: allowedAmount)
149 }
150
151 /// createNewBurner
152 ///
153 /// Function that creates and returns a new burner resource
154 ///
155 pub fun createNewBurner(): @Burner {
156 emit BurnerCreated()
157 return <-create Burner()
158 }
159 }
160
161 /// Minter
162 ///
163 /// Resource object that token admin accounts can hold to mint new tokens.
164 ///
165 pub resource Minter {
166
167 /// The amount of tokens that the minter is allowed to mint
168 pub var allowedAmount: UFix64
169
170 /// mintTokens
171 ///
172 /// Function that mints new tokens, adds them to the total supply,
173 /// and returns them to the calling context.
174 ///
175 pub fun mintTokens(amount: UFix64): @PlayToken.Vault {
176 pre {
177 amount > 0.0: "Amount minted must be greater than zero"
178 amount <= self.allowedAmount: "Amount minted must be less than the allowed amount"
179 }
180
181 PlayToken.totalSupplies.setAmountFor(timestamp: getCurrentBlock().timestamp, amount: amount)
182
183 PlayToken.totalSupply = PlayToken.totalSupply + amount
184
185 self.allowedAmount = self.allowedAmount - amount
186
187 emit TokensMinted(amount: amount)
188 return <-create Vault(balance: amount)
189 }
190
191 init(allowedAmount: UFix64) {
192 self.allowedAmount = allowedAmount
193 }
194 }
195
196 /// Burner
197 ///
198 /// Resource object that token admin accounts can hold to burn tokens.
199 ///
200 pub resource Burner {
201
202 /// burnTokens
203 ///
204 /// Function that destroys a Vault instance, effectively burning the tokens.
205 ///
206 /// Note: the burned tokens are automatically subtracted from the
207 /// total supply in the Vault destructor.
208 ///
209 pub fun burnTokens(from: @FungibleToken.Vault) {
210 let vault <- from as! @PlayToken.Vault
211 let amount = vault.balance
212 destroy vault
213 emit TokensBurned(amount: amount)
214 }
215 }
216
217 pub fun getTotalSupplyFor(timestamp: UFix64): UFix64 {
218 return self.totalSupplies.getValueOrMostRecentFor(timestamp: timestamp)
219 }
220
221 access(contract) fun destroyTotalSupply(orderedDictionary: @MoxyData.OrderedDictionary) {
222 self.totalSupplies.destroyWith(orderedDictionary: <-orderedDictionary)
223 }
224
225 pub resource interface DailyBalancesInterface {
226 pub fun getDailyBalanceFor(timestamp: UFix64): UFix64?
227 pub fun getBalanceFor(timestamp: UFix64): UFix64?
228 }
229
230 pub resource interface ReceiverInterface {
231 pub fun convertedFromMOXY(from: @FungibleToken.Vault)
232 }
233
234 pub let playTokenVaultStorage: StoragePath
235 pub let playTokenAdminStorage: StoragePath
236 pub let playTokenReceiverPath: PublicPath
237 pub let playTokenReceiverInterfacePath: PublicPath
238 pub let playTokenBalancePath: PublicPath
239 pub let playTokenDailyBalancePath: PublicPath
240
241 init() {
242 // Initial total supply defined for PLAY token to starting strength
243 // of Proof of Play
244 self.totalSupply = 350000000.0
245 self.totalSupplies <- MoxyData.createNewOrderedDictionary()
246 self.totalSupplies.setAmountFor(timestamp: getCurrentBlock().timestamp, amount: self.totalSupply)
247
248 self.playTokenVaultStorage = /storage/playTokenVault
249 self.playTokenAdminStorage = /storage/playTokenAdmin
250 self.playTokenReceiverPath = /public/playTokenReceiver
251 self.playTokenReceiverInterfacePath = /public/playTokenReceiverInterface
252 self.playTokenBalancePath = /public/playTokenBalance
253 self.playTokenDailyBalancePath = /public/playTokenDailyBalancePath
254
255 // Create the Vault with the total supply of tokens and save it in storage
256 //
257 let vault <- create Vault(balance: self.totalSupply)
258 self.account.save(<-vault, to: self.playTokenVaultStorage)
259
260 // Create a public capability to the stored Vault that only exposes
261 // the `deposit` method through the `Receiver` interface
262 //
263 self.account.link<&{FungibleToken.Receiver}>(
264 self.playTokenReceiverPath ,
265 target: self.playTokenVaultStorage
266 )
267 self.account.link<&{PlayToken.ReceiverInterface}>(
268 self.playTokenReceiverInterfacePath ,
269 target: self.playTokenVaultStorage
270 )
271
272 // Create a public capability to the stored Vault that only exposes
273 // the `balance` field through the `Balance` interface
274 //
275 self.account.link<&PlayToken.Vault{FungibleToken.Balance}>(
276 self.playTokenBalancePath,
277 target: self.playTokenVaultStorage
278 )
279
280 self.account.link<&PlayToken.Vault{FungibleToken.Balance}>(
281 self.playTokenDailyBalancePath,
282 target: self.playTokenVaultStorage
283 )
284
285 let admin <- create Administrator()
286 self.account.save(<-admin, to: self.playTokenAdminStorage)
287
288 // Emit an event that shows that the contract was initialized
289 //
290 emit TokensInitialized(initialSupply: self.totalSupply)
291 }
292}
293
294