Smart Contract
PPPV3
A.3ddb31a3880d1d8f.PPPV3
1/**
2
3#
4
5*/
6
7import FungibleToken from 0xf233dcee88fe0abe
8// Lending
9import LendingInterfaces from 0x2df970b6cdee5735
10import LendingConfig from 0x2df970b6cdee5735
11// Swap
12import SwapConfig from 0xb78ef7afa52ff906
13import SwapInterfaces from 0xb78ef7afa52ff906
14// Liquid Staking
15import LiquidStaking from 0xd6f80565193ad727
16// Farm
17import Staking from 0x1b77ba4b414de352
18// Oracle
19import PublicPriceOracle from 0xec67451f8a58216a
20
21import RV2 from 0x3ddb31a3880d1d8f
22
23
24pub contract PPPV3 {
25
26 //
27 access(all) var _totalSupply: UFix64
28
29 //
30 access(self) let _pointsbase: {Address: UFix64}
31
32 //
33 access(self) let _pointsHistorySnapshot: {Address: UFix64}
34
35 // {Referrer: TotalPointsFromReferees}
36 access(self) let _totalPointsAsReferrer: {Address: UFix64}
37
38 // {Referrer: {Referee: PointsFrom}}
39 access(self) let _pointsFromReferees: {Address: {Address: UFix64}}
40
41 //
42 access(self) let _pointsAsReferee: {Address: UFix64}
43
44
45 access(self) let _userBlacklist: {Address: Bool}
46
47 //
48 access(self) let _swapPoolWhitelist: {Address: Bool} // {PoolAddress}
49
50 /* {
51 "LendingSupply": {
52 0.0 : 0.001, // 0.0 ~ 1000.0 -> 0.001
53 1000.0 : 0.002, // 1000.0 ~ 10000.0 -> 0.002
54 10000.0 : 0.003 // 10000.0 ~ Max -> 0.003
55 }
56 }
57 */
58 access(self) let _pointsRatePerDay: {String: AnyStruct}
59
60 //access(self) let _leaderBoard: [AnyStruct]
61
62 access(self) let _userStates: {Address: {String: UFix64}}
63
64 access(self) let _secondsPerDay: UFix64
65
66 //
67 access(self) var _swapPoolAddress: Address
68 access(self) var _swapVolumeTrackingTimestamp: UFix64
69 access(self) var _swapPoolReserve0: UFix64
70 access(self) var _swapPoolReserve1: UFix64
71
72 access(self) var _topUsers: [Address]
73
74
75 access(self) let _reservedFields: {String: AnyStruct}
76
77 /// Events
78 access(all) event PointsMinted(userAddr: Address, amount: UFix64, source: String, param: {String: String})
79 access(all) event PointsBurned(userAddr: Address, amount: UFix64, source: String, param: {String: String})
80 access(all) event StateUpdated(userAddr: Address, state: {String: UFix64})
81 access(all) event PointsRateChanged(source: String, ori: UFix64, new: UFix64)
82 access(all) event PointsTierRateChanged(source: String, ori: {UFix64: UFix64}, new: {UFix64: UFix64})
83 access(all) event TopUsersChanged(ori: [Address], new: [Address])
84
85 //
86 access(all) view fun balanceOf(_ userAddr: Address): UFix64 {
87 return self.getBasePoints(userAddr)
88 +
89 self.getHistorySnapshotPoints(userAddr)
90 +
91 self.getPointsAsReferrer(userAddr)
92 +
93 self.getPointsAsReferee(userAddr)
94 +
95 self.calculateNewPointsSinceLastUpdate(userAddr: userAddr) as! UFix64 // accured points
96 }
97
98 // Mint Points
99 access(self) fun _mint(targetAddr: Address, amount: UFix64) {
100 if self._userBlacklist.containsKey(targetAddr) {
101 return
102 }
103
104 // mint points
105 if self._pointsbase.containsKey(targetAddr) == false {
106 self._pointsbase[targetAddr] = 0.0
107 }
108 self._pointsbase[targetAddr] = self._pointsbase[targetAddr]! + amount
109
110 let referrerAddr: Address? = RV2.getReferrerByReferee(referee: targetAddr)
111 if referrerAddr != nil {
112 // referee boost
113 let boostAmountAsReferee = amount * self.getPointsRate_RefereeUp()
114 if boostAmountAsReferee > 0.0 {
115 if self._pointsAsReferee.containsKey(targetAddr) == false {
116 self._pointsAsReferee[targetAddr] = boostAmountAsReferee
117 } else {
118 self._pointsAsReferee[targetAddr] = self._pointsAsReferee[targetAddr]! + boostAmountAsReferee
119 }
120 emit PointsMinted(
121 userAddr: targetAddr,
122 amount: boostAmountAsReferee,
123 source: "AsReferee",
124 param: {}
125 )
126 self._totalSupply = self._totalSupply + boostAmountAsReferee
127 }
128
129 // referrer boost
130 let boostAmountForReferrer = amount * self.getPointsRate_ReferrerUp()
131 if boostAmountForReferrer > 0.0 && self._userBlacklist.containsKey(referrerAddr!) == false {
132 if self._totalPointsAsReferrer.containsKey(referrerAddr!) == false {
133 self._totalPointsAsReferrer[referrerAddr!] = boostAmountForReferrer
134 } else {
135 self._totalPointsAsReferrer[referrerAddr!] = self._totalPointsAsReferrer[referrerAddr!]! + boostAmountForReferrer
136 }
137 if self._pointsFromReferees.containsKey(referrerAddr!) == false {
138 self._pointsFromReferees[referrerAddr!] = {}
139 }
140 if self._pointsFromReferees[referrerAddr!]!.containsKey(targetAddr) == false {
141 self._pointsFromReferees[referrerAddr!]!.insert(key: targetAddr, boostAmountForReferrer)
142 } else {
143 self._pointsFromReferees[referrerAddr!]!.insert(key: targetAddr, self._pointsFromReferees[referrerAddr!]![targetAddr]! + boostAmountForReferrer)
144 }
145 if self._pointsbase.containsKey(referrerAddr!) == false {
146 self._pointsbase[referrerAddr!] = 0.0
147 }
148 emit PointsMinted(
149 userAddr: referrerAddr!,
150 amount: boostAmountForReferrer,
151 source: "AsReferrer",
152 param: {}
153 )
154 self._totalSupply = self._totalSupply + boostAmountForReferrer
155 }
156
157 }
158
159 // update total supply
160 self._totalSupply = self._totalSupply + amount;
161 }
162
163 //
164 access(all) view fun calculateNewPointsSinceLastUpdate(userAddr: Address): AnyStruct {
165 let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
166 if lastUpdateTimestamp == 0.0 { return 0.0 }
167
168 // Lending Supply
169 let accuredLendingSupplyPoints = self.calculateNewPointsSinceLastUpdate_LendingSupply(userAddr: userAddr)
170 // Lending Borrow
171 let accuredLendingBorrowPoints = self.calculateNewPointsSinceLastUpdate_LendingBorrow(userAddr: userAddr)
172 // stFlow Holdings
173 let accuredStFlowHoldingPoints = self.calculateNewPointsSinceLastUpdate_stFlowHolding(userAddr: userAddr)
174 // Swap LP
175 let accuredSwapLPPoints = self.calculateNewPointsSinceLastUpdate_SwapLP(userAddr: userAddr)
176
177 let referrerAddr: Address? = RV2.getReferrerByReferee(referee: userAddr)
178
179 return (accuredLendingSupplyPoints + accuredLendingBorrowPoints + accuredStFlowHoldingPoints + accuredSwapLPPoints) * (referrerAddr==nil? 1.0 : 1.0+self.getPointsRate_RefereeUp())
180 }
181
182 access(all) fun updateUserState(userAddr: Address) {
183 if self._userBlacklist.containsKey(userAddr) {
184 return
185 }
186
187 let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
188 let duration = getCurrentBlock().timestamp - lastUpdateTimestamp
189 let durationStr = duration.toString()
190
191 if duration > 0.0 {
192 // Lending Supply
193 let accuredLendingSupplyPoints = self.calculateNewPointsSinceLastUpdate_LendingSupply(userAddr: userAddr)
194 if (accuredLendingSupplyPoints > 0.0) {
195 emit PointsMinted(
196 userAddr: userAddr,
197 amount: accuredLendingSupplyPoints,
198 source: "LendingSupply",
199 param: {
200 "SupplyUsdValue": self.getUserState_LendingSupply(userAddr: userAddr).toString(),
201 "Duration": durationStr
202 }
203 )
204 }
205
206 // Lending Borrow
207 let accuredLendingBorrowPoints = self.calculateNewPointsSinceLastUpdate_LendingBorrow(userAddr: userAddr)
208 if (accuredLendingBorrowPoints > 0.0) {
209 emit PointsMinted(
210 userAddr: userAddr,
211 amount: accuredLendingBorrowPoints,
212 source: "LendingBorrow",
213 param: {
214 "BorrowUsdValue": self.getUserState_LendingBorrow(userAddr: userAddr).toString(),
215 "Duration": durationStr
216 }
217 )
218 }
219
220 // stFlow Holding
221 let accuredStFlowHoldingPoints = self.calculateNewPointsSinceLastUpdate_stFlowHolding(userAddr: userAddr)
222 if (accuredStFlowHoldingPoints > 0.0) {
223 emit PointsMinted(
224 userAddr: userAddr,
225 amount: accuredStFlowHoldingPoints,
226 source: "stFlowHolding",
227 param: {
228 "stFlowHoldingBalance": self.getUserState_stFlowHolding(userAddr: userAddr).toString(),
229 "Duration": durationStr
230 }
231 )
232 }
233
234 // Swap LP
235 let accuredSwapLPPoints = self.calculateNewPointsSinceLastUpdate_SwapLP(userAddr: userAddr)
236 if (accuredSwapLPPoints > 0.0) {
237 emit PointsMinted(
238 userAddr: userAddr,
239 amount: accuredSwapLPPoints,
240 source: "SwapLP",
241 param: {
242 "SwapLPUsdValue": self.getUserState_SwapLPUsd(userAddr: userAddr).toString(),
243 "Duration": durationStr
244 }
245 )
246 }
247
248 // Mint Points
249 let accuredPointsToMint =
250 accuredLendingSupplyPoints +
251 accuredLendingBorrowPoints +
252 accuredStFlowHoldingPoints +
253 accuredSwapLPPoints
254 if (accuredPointsToMint > 0.0) {
255 self._mint(targetAddr: userAddr, amount: accuredPointsToMint)
256 }
257 }
258
259 //
260 let states = self.fetchOnchainUserStates(userAddr: userAddr)
261 let totalSupplyAmountInUsd = states[0]
262 let totalBorrowAmountInUsd = states[1]
263 let stFlowTotalBalance = states[2]
264 let totalLpBalanceUsd = states[3]
265 let totalLpAmount = states[4]
266
267 // Update State
268 if self._userStates.containsKey(userAddr)
269 || totalSupplyAmountInUsd > 0.0 || totalBorrowAmountInUsd > 0.0
270 || stFlowTotalBalance > 0.0
271 || totalLpBalanceUsd > 0.0
272 {
273 if self._userStates.containsKey(userAddr) == false {
274 self._userStates[userAddr] = {}
275 }
276
277 self.setUserState_LendingSupply(userAddr: userAddr, supplyAmount: totalSupplyAmountInUsd)
278 self.setUserState_LendingBorrow(userAddr: userAddr, borrowAmount: totalBorrowAmountInUsd)
279 self.setUserState_stFlowHolding(userAddr: userAddr, stFlowBalance: stFlowTotalBalance)
280 self.setUserState_SwapLPUsd(userAddr: userAddr, lpUsd: totalLpBalanceUsd)
281 self.setUserState_SwapLPAmount(userAddr: userAddr, lpAmount: totalLpAmount)
282
283 self.setUserState_LastUpdateTimestamp(userAddr: userAddr, timestamp: getCurrentBlock().timestamp)
284
285 //
286 emit StateUpdated(userAddr: userAddr, state: self._userStates[userAddr]!)
287 }
288 }
289
290 access(all) fun beginVolumeTracking(swapPoolAddr: Address) {
291 if self._swapPoolWhitelist.containsKey(swapPoolAddr) == false { return }
292
293 let poolInfo: [AnyStruct] = getAccount(swapPoolAddr).getCapability<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath).borrow()!.getPairInfo()
294 self._swapPoolReserve0 = poolInfo[2] as! UFix64
295 self._swapPoolReserve1 = poolInfo[3] as! UFix64
296
297 self._swapVolumeTrackingTimestamp = getCurrentBlock().timestamp
298 self._swapPoolAddress = swapPoolAddr
299 }
300
301 access(all) fun endVolumeTrackingAndMintPoints(userAddr: Address) {
302 if self._swapVolumeTrackingTimestamp != getCurrentBlock().timestamp {
303 return
304 }
305 self._swapVolumeTrackingTimestamp = 0.0
306
307 if self._userBlacklist.containsKey(userAddr) {
308 return
309 }
310
311 let poolInfo: [AnyStruct] = getAccount(self._swapPoolAddress).getCapability<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath).borrow()!.getPairInfo()
312 let reserve0Token = poolInfo[0] as! String
313 let reserve1Token = poolInfo[1] as! String
314 let curReserve0 = poolInfo[2] as! UFix64
315 let curReserve1 = poolInfo[3] as! UFix64
316
317 // Add/Remove Lp won't mint any points
318 if curReserve0 >= self._swapPoolReserve0 && curReserve1 >= self._swapPoolReserve1 { return }
319 if curReserve0 <= self._swapPoolReserve0 && curReserve1 <= self._swapPoolReserve1 { return }
320 var amountIn = 0.0
321 var amountOut = 0.0
322 var tokenInKey = ""
323 var tokenOutKey = ""
324 // Swap A to B
325 if curReserve0 > self._swapPoolReserve0 && curReserve1 < self._swapPoolReserve1 {
326 amountIn = curReserve0 - self._swapPoolReserve0
327 tokenInKey = reserve0Token
328 amountOut = self._swapPoolReserve1 - curReserve1
329 tokenOutKey = reserve1Token
330 }
331 // Swap B to A
332 if curReserve0 < self._swapPoolReserve0 && curReserve1 > self._swapPoolReserve1 {
333 amountIn = curReserve1 - self._swapPoolReserve1
334 tokenInKey = reserve1Token
335 amountOut = self._swapPoolReserve0 - curReserve0
336 tokenOutKey = reserve0Token
337 }
338
339 // Cal volume
340 let usdcPrice = PublicPriceOracle.getLatestPrice(oracleAddr: 0xf5d12412c09d2470)
341 let flowPrice = PublicPriceOracle.getLatestPrice(oracleAddr: 0xe385412159992e11)
342 let stflowPrice = PublicPriceOracle.getLatestPrice(oracleAddr: 0x031dabc5ba1d2932)
343 var volumeUsd = 0.0
344 if tokenInKey == "A.b19436aae4d94622.FiatToken" { volumeUsd = amountIn * usdcPrice }
345 else if tokenInKey == "A.1654653399040a61.FlowToken" { volumeUsd = amountIn * flowPrice }
346 else if tokenInKey == "A.d6f80565193ad727.stFlowToken" { volumeUsd = amountIn * stflowPrice }
347 else if tokenOutKey == "A.b19436aae4d94622.FiatToken" { volumeUsd = amountOut * usdcPrice }
348 else if tokenOutKey == "A.1654653399040a61.FlowToken" { volumeUsd = amountOut * flowPrice }
349 else if tokenOutKey == "A.d6f80565193ad727.stFlowToken" { volumeUsd = amountOut * stflowPrice }
350
351 // Mint points
352 let mintAmountBySwapVolume = volumeUsd * self.getPointsRate_SwapVolume()
353 if mintAmountBySwapVolume > 0.0 {
354 emit PointsMinted(
355 userAddr: userAddr,
356 amount: mintAmountBySwapVolume,
357 source: "SwapVolume",
358 param: {
359 "TokenInKey": tokenInKey,
360 "TokenOutKey": tokenOutKey,
361 "AmountIn": amountIn.toString(),
362 "AmountOut": amountOut.toString(),
363 "VolumeUsd": volumeUsd.toString()
364 }
365 )
366 self._mint(targetAddr: userAddr, amount: mintAmountBySwapVolume)
367
368 if self._userStates.containsKey(userAddr) == false {
369 self._userStates[userAddr] = {}
370 }
371 self.setUserState_SwapVolume(userAddr: userAddr, volume: self.getUserState_SwapVolume(userAddr: userAddr) + volumeUsd)
372 emit StateUpdated(userAddr: userAddr, state: self._userStates[userAddr]!)
373 }
374 }
375
376 access(all) view fun getBasePoints(_ userAddr: Address): UFix64 {
377 return self._pointsbase.containsKey(userAddr)? self._pointsbase[userAddr]! : 0.0
378 }
379
380 access(all) view fun getHistorySnapshotPoints(_ userAddr: Address): UFix64 {
381 return self._pointsHistorySnapshot.containsKey(userAddr)? self._pointsHistorySnapshot[userAddr]! : 0.0
382 }
383
384 access(all) view fun getPointsAsReferrer(_ userAddr: Address): UFix64 {
385 return self._totalPointsAsReferrer.containsKey(userAddr)? self._totalPointsAsReferrer[userAddr]!: 0.0
386 }
387
388 access(all) view fun getPointsAsReferee(_ userAddr: Address): UFix64 {
389 return self._pointsAsReferee.containsKey(userAddr)? self._pointsAsReferee[userAddr]!: 0.0
390 }
391
392 access(all) view fun getPointsAndTimestamp(_ userAddr: Address): [UFix64; 3] {
393 return [self.balanceOf(userAddr), self.getPointsAsReferrer(userAddr), self.getUserState_LastUpdateTimestamp(userAddr: userAddr)]
394 }
395
396 access(all) view fun getBasePointsLength(): Int {
397 return self._pointsbase.length
398 }
399
400 access(all) view fun getUserInfo(_ userAddr: Address): [UFix64] {
401 return [
402 self.balanceOf(userAddr),
403 self.getPointsAsReferrer(userAddr),
404 self.getUserState_LastUpdateTimestamp(userAddr: userAddr),
405 self.getUserState_LendingSupply(userAddr: userAddr),
406 self.getUserState_LendingBorrow(userAddr: userAddr),
407 self.getUserState_stFlowHolding(userAddr: userAddr),
408 self.getUserState_SwapLPUsd(userAddr: userAddr),
409 self.getUserState_SwapVolume(userAddr: userAddr)
410 ]
411 }
412
413 access(all) view fun getUserInfosLength(): Int {
414 return self._userStates.length
415 }
416
417 access(all) view fun getSlicedUserInfos(from: Int, to: Int): {Address: [UFix64]} {
418 let len = self._userStates.length
419 let endIndex = to > len ? len : to
420 var curIndex = from
421 let res: {Address: [UFix64]} = {}
422 while curIndex < endIndex {
423 let userAddr: Address = self._userStates.keys[curIndex]
424 res[userAddr] = self.getUserInfo(userAddr)
425 curIndex = curIndex + 1
426 }
427 return res
428 }
429
430 access(all) view fun getSlicedBalances(from: Int, to: Int): {Address: UFix64} {
431 let len = self._pointsbase.length
432 let endIndex = to > len ? len : to
433 var curIndex = from
434 let res: {Address: UFix64} = {}
435 while curIndex < endIndex {
436 let key: Address = self._pointsbase.keys[curIndex]
437 res[key] = self.balanceOf(key)
438 curIndex = curIndex + 1
439 }
440 return res
441 }
442
443 access(all) view fun getSlicedPointsAndTimestamp(from: Int, to: Int): {Address: [UFix64; 3]} {
444 let len = self._pointsbase.length
445 let endIndex = to > len ? len : to
446 var curIndex = from
447 let res: {Address: [UFix64; 3]} = {}
448 while curIndex < endIndex {
449 let key: Address = self._pointsbase.keys[curIndex]
450 res[key] = self.getPointsAndTimestamp(key)
451 curIndex = curIndex + 1
452 }
453 return res
454 }
455
456 access(all) view fun fetchOnchainUserStates(userAddr: Address): [UFix64] {
457 // Oracle Price
458 let oraclePrices: {String: UFix64} = { // OracleAddress -> Token Price
459 "Flow": PublicPriceOracle.getLatestPrice(oracleAddr: 0xe385412159992e11),
460 "stFlow": PublicPriceOracle.getLatestPrice(oracleAddr: 0x031dabc5ba1d2932),
461 "USDC": PublicPriceOracle.getLatestPrice(oracleAddr: 0xf5d12412c09d2470)
462 }
463
464 // Lending State
465 let lendingComptrollerRef = getAccount(0xf80cb737bfe7c792).getCapability<&{LendingInterfaces.ComptrollerPublic}>(LendingConfig.ComptrollerPublicPath).borrow()!
466 let marketAddrs: [Address] = lendingComptrollerRef.getAllMarkets()
467 let lendingOracleRef = getAccount(0x72d3a05910b6ffa3).getCapability<&{LendingInterfaces.OraclePublic}>(LendingConfig.OraclePublicPath).borrow()!
468 var totalSupplyAmountInUsd = 0.0
469 var totalBorrowAmountInUsd = 0.0
470 for poolAddr in marketAddrs {
471 let poolRef = lendingComptrollerRef.getPoolPublicRef(poolAddr: poolAddr)
472 let poolOraclePrice = lendingOracleRef.getUnderlyingPrice(pool: poolAddr)
473 let res: [UInt256; 5] = poolRef.getAccountRealtimeScaled(account: userAddr)
474 let supplyAmount = SwapConfig.ScaledUInt256ToUFix64(res[0] * res[1] / SwapConfig.scaleFactor)
475 let borrowAmount = SwapConfig.ScaledUInt256ToUFix64(res[2])
476 totalSupplyAmountInUsd = totalSupplyAmountInUsd + supplyAmount * poolOraclePrice
477 totalBorrowAmountInUsd = totalBorrowAmountInUsd + borrowAmount * poolOraclePrice
478 }
479
480 // Liquid Staking State
481 // stFlow
482 var stFlowTotalBalance = 0.0
483 let stFlowVaultCap = getAccount(userAddr).getCapability<&{FungibleToken.Balance}>(/public/stFlowTokenBalance)
484 if stFlowVaultCap.check() {
485 // Prevent fake stFlow token vault
486 if stFlowVaultCap.borrow()!.getType().identifier == "A.d6f80565193ad727.stFlowToken.Vault" {
487 stFlowTotalBalance = stFlowVaultCap.borrow()!.balance
488 }
489 }
490
491 // Swap LP in Balance
492 let lpPrices: {Address: UFix64} = {}
493 var totalLpBalanceUsd = 0.0
494 var totalLpAmount = 0.0
495 let lpTokenCollectionCap = getAccount(userAddr).getCapability<&{SwapInterfaces.LpTokenCollectionPublic}>(SwapConfig.LpTokenCollectionPublicPath)
496 if lpTokenCollectionCap.check() {
497 // Prevent fake lp token vault
498 if lpTokenCollectionCap.borrow()!.getType().identifier == "A.b063c16cac85dbd1.SwapFactory.LpTokenCollection" {
499 let lpTokenCollectionRef = lpTokenCollectionCap.borrow()!
500 let liquidityPairAddrs = lpTokenCollectionRef.getAllLPTokens()
501 for pairAddr in liquidityPairAddrs {
502 //
503 if self._swapPoolWhitelist.containsKey(pairAddr) == false {
504 continue
505 }
506
507 var lpTokenAmount = lpTokenCollectionRef.getLpTokenBalance(pairAddr: pairAddr)
508 let pairInfo = getAccount(pairAddr).getCapability<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair).borrow()!.getPairInfo()
509 // Cal lp price
510 var lpPrice = 0.0
511 if lpPrices.containsKey(pairAddr) {
512 lpPrice = lpPrices[pairAddr]!
513 } else {
514 lpPrice = self.calValidLpPrice(pairInfo: pairInfo, oraclePrices: oraclePrices)
515 lpPrices[pairAddr] = lpPrice
516 }
517 if lpPrice == 0.0 || lpTokenAmount == 0.0 { continue }
518 totalLpBalanceUsd = totalLpBalanceUsd + lpPrice * lpTokenAmount
519 totalLpAmount = totalLpAmount + lpTokenAmount
520 }
521 }
522 }
523
524 // Swap LP in Farm & stFlow in Farm
525 let farmCollectionRef = getAccount(0x1b77ba4b414de352).getCapability<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath).borrow()!
526 let userFarmIds = Staking.getUserStakingIds(address: userAddr)
527 for farmPoolId in userFarmIds {
528 let farmPool = farmCollectionRef.getPool(pid: farmPoolId)
529 let farmPoolInfo = farmPool.getPoolInfo()
530 let userInfo = farmPool.getUserInfo(address: userAddr)!
531 if farmPoolInfo.status == "0" || farmPoolInfo.status == "1" || farmPoolInfo.status == "2" {
532 let acceptTokenKey = farmPoolInfo.acceptTokenKey
533 let acceptTokenName = acceptTokenKey.slice(from: 19, upTo: acceptTokenKey.length)
534 let userFarmAmount = userInfo.stakingAmount
535 // add stFlow holding balance
536 if acceptTokenKey == "A.d6f80565193ad727.stFlowToken" {
537 stFlowTotalBalance = stFlowTotalBalance + userFarmAmount
538 continue
539 }
540 if userFarmAmount == 0.0 {
541 continue
542 }
543 // add lp holding balance
544 let swapPoolAddress = self.type2address(acceptTokenKey)
545 if acceptTokenName == "SwapPair" {
546 let swapPoolInfo = getAccount(swapPoolAddress).getCapability<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath).borrow()!.getPairInfo()
547 var lpPrice = 0.0
548 if lpPrices.containsKey(swapPoolAddress) {
549 lpPrice = lpPrices[swapPoolAddress]!
550 } else {
551 lpPrice = self.calValidLpPrice(pairInfo: swapPoolInfo, oraclePrices: oraclePrices)
552 lpPrices[swapPoolAddress] = lpPrice
553 }
554 totalLpBalanceUsd = totalLpBalanceUsd + userFarmAmount * lpPrice
555 totalLpAmount = totalLpAmount + userFarmAmount
556 }
557 }
558 }
559
560 return [
561 totalSupplyAmountInUsd,
562 totalBorrowAmountInUsd,
563 stFlowTotalBalance,
564 totalLpBalanceUsd,
565 totalLpAmount
566 ]
567 }
568
569 // Accure Lending Supply
570 access(all) view fun calculateNewPointsSinceLastUpdate_LendingSupply(userAddr: Address): UFix64 {
571 let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
572 var accuredPoints = 0.0
573 if lastUpdateTimestamp == 0.0 { return 0.0 }
574 let currUpdateTimestamp = getCurrentBlock().timestamp
575 let duration = currUpdateTimestamp - lastUpdateTimestamp
576 if duration > 0.0 {
577 let supplyAmountUsd = self.getUserState_LendingSupply(userAddr: userAddr)
578 accuredPoints = supplyAmountUsd * self.getPointsRate_LendingSupply(amount: supplyAmountUsd) / self._secondsPerDay * duration
579 }
580 return accuredPoints
581 }
582
583 // Accure Lending Borrow
584 access(all) view fun calculateNewPointsSinceLastUpdate_LendingBorrow(userAddr: Address): UFix64 {
585 let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
586 var accuredPoints = 0.0
587 if lastUpdateTimestamp == 0.0 { return 0.0 }
588 let currUpdateTimestamp = getCurrentBlock().timestamp
589 let duration = currUpdateTimestamp - lastUpdateTimestamp
590 if duration > 0.0 {
591 let borrowAmountUsd = self.getUserState_LendingBorrow(userAddr: userAddr)
592 accuredPoints = borrowAmountUsd * self.getPointsRate_LendingBorrow(amount: borrowAmountUsd) / self._secondsPerDay * duration
593 }
594 return accuredPoints
595 }
596
597 // Accure Liquid Staking - stFlowHolding
598 access(self) view fun calculateNewPointsSinceLastUpdate_stFlowHolding(userAddr: Address): UFix64 {
599 let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
600 var accuredPoints = 0.0
601 if lastUpdateTimestamp == 0.0 { return 0.0 }
602 let currUpdateTimestamp = getCurrentBlock().timestamp
603 let duration = currUpdateTimestamp - lastUpdateTimestamp
604 if duration > 0.0 {
605 let stFlowHolding = self.getUserState_stFlowHolding(userAddr: userAddr)
606 accuredPoints = stFlowHolding * self.getPointsRate_stFlowHolding(amount: stFlowHolding) / self._secondsPerDay * duration
607 }
608 return accuredPoints
609 }
610
611 // Accure Swap LP
612 access(self) view fun calculateNewPointsSinceLastUpdate_SwapLP(userAddr: Address): UFix64 {
613 let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
614 var accuredPoints = 0.0
615 if lastUpdateTimestamp == 0.0 { return 0.0 }
616 let currUpdateTimestamp = getCurrentBlock().timestamp
617 let duration = currUpdateTimestamp - lastUpdateTimestamp
618 if duration > 0.0 {
619 let swapLP = self.getUserState_SwapLPUsd(userAddr: userAddr)
620 accuredPoints = swapLP * self.getPointsRate_SwapLP(amount: swapLP) / self._secondsPerDay * duration
621 }
622 return accuredPoints
623 }
624
625 access(all) view fun getTopUsers(): [Address] { return self._topUsers }
626
627 access(all) view fun getSwapPoolWhiltlist(): {Address: Bool} { return self._swapPoolWhitelist }
628
629 access(all) view fun getUserBlacklist(): {Address: Bool} { return self._userBlacklist }
630
631 // Get Points Rate
632 access(all) view fun getPointsRate(): {String: AnyStruct} { return self._pointsRatePerDay }
633 access(all) view fun getPointsRate_LendingSupply(amount: UFix64): UFix64 { return self.calculateTierRateByAmount(amount: amount, tier: self._pointsRatePerDay["LendingSupply"]! as! {UFix64: UFix64}) }
634 access(all) view fun getPointsRate_LendingBorrow(amount: UFix64): UFix64 { return self.calculateTierRateByAmount(amount: amount, tier: self._pointsRatePerDay["LendingBorrow"]! as! {UFix64: UFix64}) }
635 access(all) view fun getPointsRate_stFlowHolding(amount: UFix64): UFix64 { return self.calculateTierRateByAmount(amount: amount, tier: self._pointsRatePerDay["stFlowHolding"]! as! {UFix64: UFix64}) }
636 access(all) view fun getPointsRate_SwapLP(amount: UFix64): UFix64 { return self.calculateTierRateByAmount(amount: amount, tier: self._pointsRatePerDay["SwapLP"]! as! {UFix64: UFix64}) }
637 access(all) view fun getPointsRate_SwapVolume(): UFix64 { return self._pointsRatePerDay["SwapVolume"]! as! UFix64 }
638 access(all) view fun getPointsRate_ReferrerUp(): UFix64 { return self._pointsRatePerDay["ReferrerUp"]! as! UFix64 }
639 access(all) view fun getPointsRate_RefereeUp(): UFix64 { return self._pointsRatePerDay["RefereeUp"]! as! UFix64 }
640
641 // Get User State
642 access(all) view fun getUserState(userAddr: Address): {String: UFix64} { return self._userStates.containsKey(userAddr)? self._userStates[userAddr]! : {} }
643 access(all) view fun getUserState_LastUpdateTimestamp(userAddr: Address): UFix64 { return self._userStates.containsKey(userAddr)? (self._userStates[userAddr]!.containsKey("LastUpdateTimestamp")? self._userStates[userAddr]!["LastUpdateTimestamp"]! : 0.0): 0.0 }
644 access(all) view fun getUserState_LendingSupply(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("LendingTotalSupplyUsd")? self._userStates[userAddr]!["LendingTotalSupplyUsd"]! : 0.0 }
645 access(all) view fun getUserState_LendingBorrow(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("LendingTotalBorrowUsd")? self._userStates[userAddr]!["LendingTotalBorrowUsd"]! : 0.0 }
646 access(all) view fun getUserState_stFlowHolding(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("stFlowHolding")? self._userStates[userAddr]!["stFlowHolding"]! : 0.0 }
647 access(all) view fun getUserState_SwapLPUsd(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("SwapLpUsd")? self._userStates[userAddr]!["SwapLpUsd"]! : 0.0 }
648 access(all) view fun getUserState_SwapLPAmount(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("SwapLpAmount")? self._userStates[userAddr]!["SwapLpAmount"]! : 0.0 }
649 access(all) view fun getUserState_SwapVolume(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("SwapVolumeUsd")? self._userStates[userAddr]!["SwapVolumeUsd"]! : 0.0 }
650
651 access(self) fun setUserState_LastUpdateTimestamp(userAddr: Address, timestamp: UFix64) { self._userStates[userAddr]!.insert(key: "LastUpdateTimestamp", timestamp) }
652 access(self) fun setUserState_LendingSupply(userAddr: Address, supplyAmount: UFix64) { self._userStates[userAddr]!.insert(key: "LendingTotalSupplyUsd", supplyAmount) }
653 access(self) fun setUserState_LendingBorrow(userAddr: Address, borrowAmount: UFix64) { self._userStates[userAddr]!.insert(key: "LendingTotalBorrowUsd", borrowAmount) }
654 access(self) fun setUserState_stFlowHolding(userAddr: Address, stFlowBalance: UFix64) { self._userStates[userAddr]!.insert(key: "stFlowHolding", stFlowBalance) }
655 access(self) fun setUserState_SwapLPUsd(userAddr: Address, lpUsd: UFix64) { self._userStates[userAddr]!.insert(key: "SwapLpUsd", lpUsd) }
656 access(self) fun setUserState_SwapLPAmount(userAddr: Address, lpAmount: UFix64) { self._userStates[userAddr]!.insert(key: "SwapLpAmount", lpAmount) }
657 access(self) fun setUserState_SwapVolume(userAddr: Address, volume: UFix64) { self._userStates[userAddr]!.insert(key: "SwapVolumeUsd", volume) }
658
659 access(all) view fun calculateTierRateByAmount(amount: UFix64, tier: {UFix64: UFix64}): UFix64 {
660 var rate = 0.0
661 var maxThreshold = 0.0
662 for threshold in tier.keys {
663 if amount >= threshold && threshold >= maxThreshold {
664 rate = tier[threshold]!
665 maxThreshold = threshold
666 }
667 }
668 return rate
669 }
670
671 access(all) view fun calValidLpPrice(pairInfo: [AnyStruct], oraclePrices: {String: UFix64}): UFix64 {
672 var reserveAmount = 0.0
673 var reservePrice = 0.0
674 var lpPrice = 0.0
675 if pairInfo[0] as! String == "A.b19436aae4d94622.FiatToken" {reserveAmount = pairInfo[2] as! UFix64; reservePrice = oraclePrices["USDC"]!}
676 else if pairInfo[1] as! String == "A.b19436aae4d94622.FiatToken" {reserveAmount = pairInfo[3] as! UFix64; reservePrice = oraclePrices["USDC"]!}
677 else if pairInfo[0] as! String == "A.1654653399040a61.FlowToken" {reserveAmount = pairInfo[2] as! UFix64; reservePrice = oraclePrices["Flow"]!}
678 else if pairInfo[1] as! String == "A.1654653399040a61.FlowToken" {reserveAmount = pairInfo[3] as! UFix64; reservePrice = oraclePrices["Flow"]!}
679 else if pairInfo[0] as! String == "A.d6f80565193ad727.stFlowToken" {reserveAmount = pairInfo[2] as! UFix64; reservePrice = oraclePrices["stFlow"]!}
680 else if pairInfo[1] as! String == "A.d6f80565193ad727.stFlowToken" {reserveAmount = pairInfo[3] as! UFix64; reservePrice = oraclePrices["stFlow"]!}
681 if reservePrice > 0.0 && reserveAmount > 1000.0 {
682 lpPrice = reserveAmount * reservePrice * 2.0 / (pairInfo[5] as! UFix64)
683 }
684 return lpPrice
685 }
686
687 access(all) view fun type2address(_ type: String) : Address {
688 let address = type.slice(from: 2, upTo: 18)
689 var r: UInt64 = 0
690 var bytes = address.decodeHex()
691 while bytes.length>0{
692 r = r + (UInt64(bytes.removeFirst()) << UInt64(bytes.length*8))
693 }
694 return Address(r)
695 }
696
697 /// Admin
698 ///
699 access(all) resource Admin {
700 // Set Points Rate
701 access(all) fun setPointsRate_stFlowHoldingTier(tierRate: {UFix64: UFix64}) {
702 emit PointsTierRateChanged(source: "stFlowHolding", ori: PPPV3._pointsRatePerDay["stFlowHolding"]! as! {UFix64: UFix64}, new: tierRate)
703 PPPV3._pointsRatePerDay["stFlowHolding"] = tierRate
704 }
705 access(all) fun setPointsRate_LendingSupplyTier(tierRate: {UFix64: UFix64}) {
706 emit PointsTierRateChanged(source: "LendingSupply", ori: PPPV3._pointsRatePerDay["LendingSupply"]! as! {UFix64: UFix64}, new: tierRate)
707 PPPV3._pointsRatePerDay["LendingSupply"] = tierRate
708 }
709 access(all) fun setPointsRate_LendingBorrowTier(tierRate: {UFix64: UFix64}) {
710 emit PointsTierRateChanged(source: "LendingBorrow", ori: PPPV3._pointsRatePerDay["LendingBorrow"]! as! {UFix64: UFix64}, new: tierRate)
711 PPPV3._pointsRatePerDay["LendingBorrow"] = tierRate
712 }
713 access(all) fun setPointsRate_SwapLPTier(tierRate: {UFix64: UFix64}) {
714 emit PointsTierRateChanged(source: "SwapLP", ori: PPPV3._pointsRatePerDay["SwapLP"]! as! {UFix64: UFix64}, new: tierRate)
715 PPPV3._pointsRatePerDay["SwapLP"] = tierRate
716 }
717 access(all) fun setPointsRate_SwapVolume(rate: UFix64) {
718 emit PointsRateChanged(source: "SwapVolume", ori: PPPV3._pointsRatePerDay["SwapVolume"]! as! UFix64, new: rate)
719 PPPV3._pointsRatePerDay["SwapVolume"] = rate
720 }
721 access(all) fun setPointsRate_ReferrerUp(rate: UFix64) {
722 emit PointsRateChanged(source: "ReferrerUp", ori: PPPV3._pointsRatePerDay["ReferrerUp"]! as! UFix64, new: rate)
723 PPPV3._pointsRatePerDay["ReferrerUp"] = rate
724 }
725 access(all) fun setPointsRate_RefereeUp(rate: UFix64) {
726 emit PointsRateChanged(source: "RefereeUp", ori: PPPV3._pointsRatePerDay["RefereeUp"]! as! UFix64, new: rate)
727 PPPV3._pointsRatePerDay["RefereeUp"] = rate
728 }
729
730 // Add Swap Pool in Whiltelist
731 access(all) fun addSwapPoolInWhiltelist(poolAddr: Address) {
732 PPPV3._swapPoolWhitelist[poolAddr] = true
733 }
734 // Remove Swap Pool in Whitelist
735 access(all) fun removeSwapPoolInWhiltelist(poolAddr: Address) {
736 PPPV3._swapPoolWhitelist.remove(key: poolAddr)
737 }
738
739 // Set history snapshot points
740 access(all) fun setHistorySnapshotPoints(userAddr: Address, newSnapshotBalance: UFix64) {
741 if PPPV3._pointsHistorySnapshot.containsKey(userAddr) == false {
742 PPPV3._pointsHistorySnapshot[userAddr] = 0.0
743 }
744 if PPPV3._pointsbase.containsKey(userAddr) == false {
745 PPPV3._pointsbase[userAddr] = 0.0
746 }
747 let preSnapshotBalance = PPPV3._pointsHistorySnapshot[userAddr]!
748 if preSnapshotBalance == newSnapshotBalance {
749 return
750 }
751 if preSnapshotBalance < newSnapshotBalance {
752 emit PointsMinted(userAddr: userAddr, amount: newSnapshotBalance - preSnapshotBalance, source: "HistorySnapshot", param: {"PreBalance": preSnapshotBalance.toString(), "NewBalance": newSnapshotBalance.toString()})
753 } else {
754 emit PointsBurned(userAddr: userAddr, amount: preSnapshotBalance - newSnapshotBalance, source: "HistorySnapshot", param: {"PreBalance": preSnapshotBalance.toString(), "NewBalance": newSnapshotBalance.toString()})
755 }
756 PPPV3._totalSupply = PPPV3._totalSupply - preSnapshotBalance + newSnapshotBalance
757 PPPV3._pointsHistorySnapshot[userAddr] = newSnapshotBalance
758 }
759
760 // Ban user
761 access(all) fun addUserBlackList(userAddr: Address) {
762 PPPV3._userBlacklist[userAddr] = true
763 }
764 access(all) fun removeUserBlackList(userAddr: Address) {
765 PPPV3._userBlacklist.remove(key: userAddr)
766 }
767 access(all) fun reconcileBasePoints(userAddr: Address, newBasePoints: UFix64) {
768 // TODO process referral points
769 if PPPV3._pointsbase.containsKey(userAddr) == false {
770 PPPV3._pointsbase[userAddr] = 0.0
771 }
772 let preBasePoints = PPPV3._pointsbase[userAddr]!
773 if preBasePoints == newBasePoints {
774 return
775 }
776 if preBasePoints < newBasePoints {
777 emit PointsMinted(userAddr: userAddr, amount: newBasePoints - preBasePoints, source: "Reconcile", param: {"PreBaseBalance": preBasePoints.toString(), "NewBaseBalance": newBasePoints.toString()})
778 } else {
779 emit PointsBurned(userAddr: userAddr, amount: preBasePoints - newBasePoints, source: "Reconcile", param: {"PreBaseBalance": preBasePoints.toString(), "NewBaseBalance": newBasePoints.toString()})
780 }
781 PPPV3._totalSupply = PPPV3._totalSupply - preBasePoints + newBasePoints
782 PPPV3._pointsbase[userAddr] = newBasePoints
783 }
784
785 access(all) fun updateTopUsers(addrs: [Address]) {
786 emit TopUsersChanged(ori: PPPV3._topUsers, new: addrs)
787 PPPV3._topUsers = addrs
788 }
789 }
790
791 init() {
792 self._totalSupply = 0.0
793 self._secondsPerDay = 86400.0
794 self._pointsbase = {}
795 self._pointsHistorySnapshot = {}
796 self._totalPointsAsReferrer = {}
797 self._pointsFromReferees = {}
798 self._pointsAsReferee = {}
799 self._topUsers = []
800 self._pointsRatePerDay = {
801 "stFlowHolding": {
802 0.0: 1.0,
803 1000.0: 2.0,
804 10000.0: 3.0
805 },
806 "LendingSupply": {
807 0.0: 0.001,
808 1000.0: 0.002,
809 10000.0: 0.003
810 },
811 "LendingBorrow": {
812 0.0: 0.002,
813 1000.0: 0.004,
814 10000.0: 0.006
815 },
816 "SwapLP": {
817 0.0: 1.0,
818 1000.0: 2.0,
819 10000.0: 3.0
820 },
821 "SwapVolume": 5.0,
822 "ReferrerUp": 0.1,
823 "RefereeUp": 0.1
824 }
825 self._swapPoolWhitelist = {
826 0xfa82796435e15832: true, // FLOW-USDC
827 0xcc96d987317f0342: true, // FLOW-ceWETH
828 0x09c49abce2a7385c: true, // FLOW-ceWBTC
829 0x396c0cda3302d8c5: true, // FLOW-stFLOW v1
830 0xc353b9d685ec427d: true, // FLOW-stFLOW stable
831 0xa06c38beec9cf0e8: true, // FLOW-DUST
832 0xbfb26bb8adf90399: true // FLOW-SLOPPY
833 }
834
835 self._userStates = {}
836 self._userBlacklist = {}
837
838 self._swapPoolAddress = 0x00
839 self._swapVolumeTrackingTimestamp = 0.0
840 self._swapPoolReserve0 = 0.0
841 self._swapPoolReserve1 = 0.0
842
843
844 self._reservedFields = {}
845
846 destroy <-self.account.load<@AnyResource>(from: /storage/pointsAdmin1)
847 self.account.save(<-create Admin(), to: /storage/pointsAdmin1)
848 }
849}
850