Smart Contract
PierLPToken
A.609e10301860b683.PierLPToken
1import MultiFungibleToken from 0xa378eeb799df8387
2
3/**
4
5PierLPToken is the LP token contract for Metapier Wharf v1.
6For each liquidity pool, we have one dedicated LP token, with
7the `tokenId` of the LP token equals to the `poolId` of the pool.
8
9@author Metapier Foundation Ltd.
10
11 */
12pub contract PierLPToken: MultiFungibleToken {
13
14 // Event that is emitted when the contract is created
15 pub event ContractInitialized()
16
17 // Event that is emitted when tokens are minted
18 pub event TokensMinted(tokenId: UInt64, amount: UFix64)
19
20 // Event that is emitted when tokens are burned
21 pub event TokensBurned(tokenId: UInt64, amount: UFix64)
22
23 // Event that is emitted when tokens are withdrawn from a Vault of some token ID
24 pub event TokensWithdrawn(tokenId: UInt64, amount: UFix64, from: Address?)
25
26 // Event that is emitted when tokens are deposited into a Vault of some token ID
27 pub event TokensDeposited(tokenId: UInt64, amount: UFix64, to: Address?)
28
29 // Event that is emitted when a new TokenMaster resource is created
30 pub event TokenMasterCreated(tokenId: UInt64)
31
32 // The common storage path for storing a PierLPToken.Collection
33 pub let CollectionStoragePath: StoragePath
34
35 // The common public path for linking to the PierLPToken.Collection{MultiFungibleToken.CollectionPublic}
36 pub let CollectionPublicPath: PublicPath
37
38 // A mapping from token ID to the corresponding total supply
39 access(contract) let totalSupply: {UInt64: UFix64}
40
41 // PierLPToken Vault
42 pub resource Vault: MultiFungibleToken.Receiver, MultiFungibleToken.View {
43
44 // The total balance of the Vault
45 pub var balance: UFix64
46
47 // The liquidity pool id that the Vault corresponds to
48 pub let tokenId: UInt64
49
50 // Vault initializer
51 //
52 // @param tokenId The liquidity pool id that the Vault corresponds to
53 // @param balance The initial balance of the Vault
54 init(tokenId: UInt64, balance: UFix64) {
55 self.tokenId = tokenId
56 self.balance = balance
57 }
58
59 // Subtracts the amount of tokens from the owner's Vault
60 // and returns a Vault with the subtracted balance.
61 //
62 // @param amount The amount of tokens to withdraw
63 // @return A new Vault (of the same token id) that contains the requested amount of tokens
64 pub fun withdraw(amount: UFix64): @PierLPToken.Vault {
65 self.balance = self.balance - amount
66 emit TokensWithdrawn(tokenId: self.tokenId, amount: amount, from: self.owner?.address)
67 return <- create Vault(tokenId: self.tokenId, balance: amount)
68 }
69
70 // Takes a Vault of the same token id and adds its
71 // balance to the balance of this Vault
72 //
73 // @param from A Vault that is ready to have its balance added to this Vault
74 pub fun deposit(from: @MultiFungibleToken.Vault) {
75 let vault <- from as! @Vault
76 self.balance = self.balance + vault.balance
77 emit TokensDeposited(tokenId: self.tokenId, amount: vault.balance, to: self.owner?.address)
78 // no need to reset the vault's balance because destroy() do not change the total supply
79 destroy vault
80 }
81
82 destroy() {
83 // Destroys a non-empty Vault will not affect the total supply but lock the balance forever instead
84 }
85 }
86
87 // A collection to help with storing and organizing multiple LP tokens.
88 pub resource Collection:
89 MultiFungibleToken.Provider,
90 MultiFungibleToken.Receiver,
91 MultiFungibleToken.CollectionPublic
92 {
93 // A mapping from token id (pool id) to LP token Vault
94 pub var vaults: @{UInt64: PierLPToken.Vault}
95
96 // For a given token id, subtracts the amount of tokens from the corresponding Vault
97 // in the collection and returns a Vault with the token id and the subtracted balance.
98 //
99 // Note: A new empty Vault will be created in the collection if the collection doesn't
100 // contain the requested LP token.
101 //
102 // @param tokenId The token id (pool id) of the Vault from which to withdraw
103 // @param amount The amount of tokens to withdraw
104 // @return A new Vault (of the same token id) that contains the requested amount of tokens
105 pub fun withdraw(tokenId: UInt64, amount: UFix64): @PierLPToken.Vault {
106 if !self.vaults.containsKey(tokenId) {
107 self.vaults[tokenId] <-! PierLPToken.createEmptyVault(tokenId: tokenId)
108 }
109
110 let vault = (&self.vaults[tokenId] as &PierLPToken.Vault?)!
111 return <- vault.withdraw(amount: amount)
112 }
113
114 // Adds the balance of the deposit Vault to the Vault with the same token id
115 // in the collection.
116 //
117 // Note: A new Vault will be created in the collection if the collection doesn't
118 // contain the given LP token.
119 //
120 // @param from The LP token Vault to deposit into the collection
121 pub fun deposit(from: @MultiFungibleToken.Vault) {
122 if from.balance == 0.0 {
123 // ignore zero-balance vaults to prevent spamming
124 destroy from
125 return
126 }
127
128 let tokenId = from.tokenId
129 if !self.vaults.containsKey(tokenId) {
130 self.vaults[tokenId] <-! PierLPToken.createEmptyVault(tokenId: tokenId)
131 }
132
133 let vault = (&self.vaults[tokenId] as &PierLPToken.Vault?)!
134 vault.deposit(from: <- from)
135 }
136
137 // Gets the token ids of all vaults stored in the collection, including the
138 // empty ones.
139 //
140 // @return An array of the token ids of all vaults stored in the collection.
141 pub fun getTokenIds(): [UInt64] {
142 return self.vaults.keys
143 }
144
145 // Checks if the internal `vaults` contains the requested LP token.
146 //
147 // @return `true` iff the internal `vaults` contains a Vault of the
148 // requested token id ()
149 pub fun hasToken(tokenId: UInt64): Bool {
150 return self.vaults.containsKey(tokenId)
151 }
152
153 // Returns a restricted Vault of the requested token id for public access,
154 // or throws an exception if it doesn't have a Vault of the requested token
155 // id
156 //
157 // @param tokenId The token id (pool id) of the Vault to query
158 // @return A Vault reference of the requested token id, which exposes only
159 // the Receiver and View
160 pub fun getPublicVault(tokenId: UInt64):
161 &PierLPToken.Vault{MultiFungibleToken.Receiver, MultiFungibleToken.View}
162 {
163 return (&self.vaults[tokenId] as &PierLPToken.Vault{MultiFungibleToken.Receiver, MultiFungibleToken.View}?)!
164 }
165
166 // Initializes the Collection
167 init() {
168 self.vaults <- {}
169 }
170
171 // Destroys the Collection and permanently locks all the stored LP tokens (if there are any)
172 destroy() {
173 destroy self.vaults
174 }
175 }
176
177 // TokenMaster is a resource to manage the LP token supply for
178 // one specific liquidity pool
179 pub resource TokenMaster {
180
181 // The id of the LP token this TokenMaster can manage
182 pub let tokenId: UInt64
183
184 // Mints the given amount of LP tokens and returns a Vault
185 // that stores the minted tokens.
186 //
187 // @param amount The amount of tokens to mint and return
188 // @return A Vault that contains the requested amount of LP
189 // tokens (of the predefined token id)
190 pub fun mintTokens(amount: UFix64): @PierLPToken.Vault {
191 pre {
192 amount > 0.0: "Metapier PierLPToken: Amount to mint must be greater than zero"
193 }
194
195 PierLPToken.totalSupply[self.tokenId] = PierLPToken.totalSupply[self.tokenId]! + amount
196 emit TokensMinted(tokenId: self.tokenId, amount: amount)
197
198 return <- create Vault(tokenId: self.tokenId, balance: amount)
199 }
200
201 // Burns the given LP token Vault by subtracting its balance
202 // from the total supply.
203 //
204 // @param vault The LP token Vault to burn (must have the
205 // expected token id)
206 pub fun burnTokens(vault: @PierLPToken.Vault) {
207 pre {
208 self.tokenId == vault.tokenId: "Metapier PierLPToken: Cannot burn other LP tokens"
209 }
210
211 PierLPToken.totalSupply[self.tokenId] = PierLPToken.totalSupply[self.tokenId]! - vault.balance
212 emit TokensBurned(tokenId: vault.tokenId, amount: vault.balance)
213
214 destroy vault
215 }
216
217 // Initializes the TokenMaster for a specific LP token
218 //
219 // @param tokenId The id of the LP token this TokenMaster can manage
220 init(tokenId: UInt64) {
221 self.tokenId = tokenId
222 }
223 }
224
225 // Admin is a resource for initializing new LP tokens
226 pub resource Admin {
227
228 // Initializes a new LP token and returns a TokenMaster for
229 // managing its total supply. Will throw an error if the token
230 // already exists.
231 //
232 // @param tokenId The id of the new LP token
233 // @return A TokenMaster for the new LP token (implied by
234 // token id)
235 pub fun initNewLPToken(tokenId: UInt64): @TokenMaster {
236 pre {
237 !PierLPToken.totalSupply.containsKey(tokenId): "Metapier PierLPToken: LP Token already exists"
238 }
239 // initializes the total supply entry
240 PierLPToken.totalSupply[tokenId] = 0.0
241 emit TokenMasterCreated(tokenId: tokenId)
242 return <- create TokenMaster(tokenId: tokenId)
243 }
244 }
245
246 // Creates an empty collection
247 //
248 // @return A new empty Collection
249 pub fun createEmptyCollection(): @PierLPToken.Collection {
250 return <- create Collection()
251 }
252
253 // Creates a new Vault of the requested token id that has a zero balance,
254 // or throws an error if the requested token id has not yet been initialized
255 //
256 // @param tokenId The token id (pool id) the new Vault is associate with
257 // @return A new empty Vault of the requested token id
258 pub fun createEmptyVault(tokenId: UInt64): @PierLPToken.Vault {
259 return <- create Vault(tokenId: tokenId, balance: 0.0)
260 }
261
262 // Gets the total supply of the requested token id, or nil if the token
263 // id has not yet been initialized
264 //
265 // @param tokenId The token id (pool id) to query
266 // @return The total supply of the requested token id, or nil if the
267 // token doesn't exist
268 pub fun getTotalSupply(tokenId: UInt64): UFix64? {
269 return self.totalSupply[tokenId]
270 }
271
272 // Initializes the contract
273 init() {
274 self.CollectionStoragePath = /storage/metapierLPTokenCollection
275 self.CollectionPublicPath = /public/metapierLPTokenCollection
276
277 self.totalSupply = {}
278
279 let admin <- create Admin()
280 self.account.save(<- admin, to: /storage/metapierLPTokenAdmin)
281
282 emit ContractInitialized()
283 }
284}
285