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