From 4f42382c6a22b56904a61ca88894fa1066ed21fe Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:13:47 +0500 Subject: [PATCH 01/86] unlock draft + some total gwx changes --- ride/boosting.ride | 61 ++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index ad440637f..162a2dfa2 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -219,8 +219,7 @@ func keyTotalMaxBoostINTEGRAL() = "%s%s__maxBoostI func keyUserBoostAvalaibleToClaimTotal(userNum: String) = makeString(["%s%d__userBoostAvaliableToClaimTotal", userNum], SEP) func keyUserBoostClaimed(userNum: String) = makeString(["%s%d__userBoostClaimed", userNum], SEP) -func keyTotalCachedGwx() = "%s%s__gwxCached__total" -func keyTotalCachedGwxCorrective() = "%s__gwxCachedTotalCorrective" +func keyGwxTotal() = "%s%s__gwx__total" # Voting emission # User vote @@ -279,20 +278,8 @@ let votingEmissionContract = factoryContract.getStringValue(keyVotingEmissionCon let boostCoeff = emissionContract.invoke("getBoostCoeffREADONLY", [], []).exactAs[Int] -func getTotalCachedGwx(correct: Boolean) = { - let currentEpochUi = votingEmissionContract.getIntegerValue(keyCurrentEpochUi()) - - let keyTargetEpoch = ["%s%s", "totalCachedGwxCorrection__activationEpoch"].makeString(SEP) - let targetEpochOption = this.getInteger(keyTargetEpoch) - - let totalCachedGwxRaw = this.getInteger(keyTotalCachedGwx()).valueOrElse(0) - - let isCorrectionActivated = targetEpochOption.isDefined() && (currentEpochUi >= targetEpochOption.value()) - let corrective = if (isCorrectionActivated && correct) then { - this.getInteger(keyTotalCachedGwxCorrective()).valueOrElse(0) - } else 0 - - max([0, totalCachedGwxRaw + corrective]) +func getGwxTotal() = { + this.getInteger(keyGwxTotal()).valueOrElse(0) } func HistoryEntry(type: String, user: String, amount: Int, lockStart: Int, duration: Int, k: Int, b: Int, i: Invocation) = { @@ -653,7 +640,7 @@ func lockActions(i: Invocation, duration: Int) = { let b = gWxParamsResultList[1].ai() let period = gWxParamsResultList[2].ai().toString() - let totalCachedGwxRaw = getTotalCachedGwx(false) + let totalCachedGwxRaw = getGwxTotal() let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNumStr) let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 @@ -668,7 +655,7 @@ func lockActions(i: Invocation, duration: Int) = { :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, duration, k, b, i) ++ [ IntegerEntry(userBoostEmissionLastIntegralKEY, boostEmissionIntegral), - IntegerEntry(keyTotalCachedGwx(), totalCachedGwxRaw + gWxAmountStart) + IntegerEntry(keyGwxTotal(), totalCachedGwxRaw + gWxAmountStart) ], gWxAmountStart) } @@ -748,14 +735,13 @@ func increaseLock(deltaDuration: Int) = { let currUserGwx = calcCurrentGwxAmount(userAddressStr) let gwxDiff = gWxAmountStart - currUserGwx if (gwxDiff < 0) then throwErr("gwxDiff is less then 0: " + gwxDiff.toString()) else - let totalCachedGwxRaw = getTotalCachedGwx(false) - let totalCachedGwxCorrected = getTotalCachedGwx(true) + let totalCachedGwxRaw = getGwxTotal() LockParamsEntry(userAddressStr, userNumStr, userAmountNew, lockStartNew, lockDurationNew, k, b, period) ++ StatsEntry(pmtAmount, deltaDuration, 0, 0) :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, lockDurationNew, k, b, i) ++ [ - IntegerEntry(keyTotalCachedGwx(), totalCachedGwxRaw + gwxDiff) + IntegerEntry(keyGwxTotal(), totalCachedGwxRaw + gwxDiff) ] } @@ -782,11 +768,38 @@ func unlock(userAddress: String) = { let lockStart = userRecordArray[IdxLockStart].parseIntValue() let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() let lockEnd = lockStart + lockDuration + # TODO: save claimed amount + let userClaimed = 0 + + let blocksInDay = 24 * 60 + let t = (height - lockStart) / blocksInDay + let daysInYearX8 = fraction(3652425, MULT8, 10000) + # let blocksInYearX8 = daysInYearX8 * blocksInDay + # let monthsInYear = 12 + # let blockInMonthX8 = blocksInYearX8 / monthsInYear + let exponent = fraction( + t, + 8 * blocksInDay * MULT8, + lockDuration + ) + let wxWithdrawable = if (height > lockEnd) then { + userAmount - userClaimed + } else { + fraction( + userAmount, + MULT8 - pow(5, 1, exponent, SCALE8, SCALE8, DOWN), + MULT8 + ) + } + + let gwxBurned = max([ + wxWithdrawable, + fraction(t, userAmount * MULT8, 4 * daysInYearX8) + ]) let cfgArray = readConfigArrayOrFail() let assetId = cfgArray[IdxCfgAssetId].fromBase58String() - if (lockEnd >= height) then throwErr("wait " + lockEnd.toString() + " to unlock") else if (userAmount <= 0) then throwErr("nothing to unlock") else let period = mathContract.getInteger(keyNextPeriod()).valueOrElse(0) @@ -832,8 +845,8 @@ func getUserGwxAmountAtHeightREADONLY(userAddress: String, targetHeight: Int) = } @Callable(i) -func getTotalCachedGwxREADONLY() = { - ([], getTotalCachedGwx(true)) +func getGwxTotalREADONLY() = { + ([], getGwxTotal()) } # call this function when wxEmissionRate or boostCoeff changes From bb2acb1a099b92f89fec0b85f72a507d12274c58 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:20:03 +0500 Subject: [PATCH 02/86] lockedGwxAmount draft --- ride/boosting.ride | 102 +++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 162a2dfa2..7380886f6 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -13,8 +13,15 @@ let SEP = "__" let SCALE8 = 8 let MULT8 = 100000000 let POOLWEIGHTMULT = MULT8 +let contractFilename = "boosting.ride" -func wrapErr(msg: String) = ["boosting.ride:", msg].makeString(" ") +let blocksInDay = 24 * 60 +let daysInYearX8 = fraction(3652425, MULT8, 10000) +let blocksInYearX8 = daysInYearX8 * blocksInDay +let monthsInYear = 12 +let blockInMonthX8 = blocksInYearX8 / monthsInYear + +func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") func throwErr(msg: String) = msg.wrapErr().throw() # getStringOrFail @@ -43,6 +50,10 @@ func ai(val: Any) = { } } +func ensurePositive(v: Int, m: String|Unit) = { + if (v >= 0) then v else throwErr(m.valueOrElse("value") + " should be positive") +} + func keyReferralsContractAddress() = ["%s%s", "config", "referralsContractAddress"].makeString(SEP) let referralsContractAddressOrFail = this.strf(keyReferralsContractAddress()).addressFromStringValue() @@ -282,6 +293,11 @@ func getGwxTotal() = { this.getInteger(keyGwxTotal()).valueOrElse(0) } +# TODO: should returns locked gwx amount +func getLockedGwxAmount(userAddress: Address) = { + 0 +} + func HistoryEntry(type: String, user: String, amount: Int, lockStart: Int, duration: Int, k: Int, b: Int, i: Invocation) = { let historyKEY = makeString(["%s%s%s%s__history", type, user, i.transactionId.toBase58String()], SEP) let historyDATA = makeString([ @@ -314,10 +330,12 @@ func StatsEntry(totalLockedInc: Int, durationInc: Int, lockCountInc: Int, usersC IntegerEntry(totalAmountKEY, totalAmount + totalLockedInc)] } -# TODO MOVE INTO MATH CONTRACT -func calcGwxAmount(kRaw: Int, bRaw: Int, h: Int) = { - let SCALE = 1000 # see math contract - (kRaw * h + bRaw) / SCALE +func calcGwxAmount(amount: Int, duration: Int) = { + fraction( + amount, + duration * MULT8, + 4 * blocksInYearX8 + ) } func LockParamsEntry(userAddress: String, userNum: String, amount: Int, start: Int, duration: Int, k: Int, b: Int, period: String) = { @@ -330,8 +348,7 @@ func LockParamsEntry(userAddress: String, userNum: String, amount: Int, start: I let kByPeriodKEY = keyLockParamByPeriodK(userNum, period) let bByPeriodKEY = keyLockParamByPeriodB(userNum, period) - # TODO think about moving to another place - let gwxAmount = calcGwxAmount(k, b, height) + let gwxAmount = calcGwxAmount(amount, duration) [IntegerEntry(userAmountKEY, amount), IntegerEntry(startBlockKEY, start), IntegerEntry(durationKEY, duration), @@ -352,22 +369,9 @@ func extractOptionalPaymentAmountOrFail(i: Invocation, expectedAssetId: ByteVect pmt.amount } -func calcUserGwxAmountAtHeight(userAddress: String, targetHeight: Int) = { - let EMPTY = "empty" - let user2NumMappingKEY = keyUser2NumMapping(userAddress) - let userNum = user2NumMappingKEY.getString().valueOrElse(EMPTY) - - let k = keyLockParamK(userNum).getInteger().valueOrElse(0) - let b = keyLockParamB(userNum).getInteger().valueOrElse(0) - - let gwxAmountCalc = calcGwxAmount(k, b, targetHeight) - let gwxAmount = if (gwxAmountCalc < 0 ) then 0 else gwxAmountCalc - - gwxAmount -} - -func calcCurrentGwxAmount(userAddress: String) = { - userAddress.calcUserGwxAmountAtHeight(height) +# TODO: parse state and return user's current gwx amount +func getGwxAmount(userAddress: Address) = { + 0 } func getVotingEmissionEpochInfo() = { @@ -702,7 +706,8 @@ func increaseLock(deltaDuration: Int) = { let pmtAmount = extractOptionalPaymentAmountOrFail(i, assetId) - let userAddressStr = i.caller.toString() + let userAddress = i.caller + let userAddressStr = userAddress.toString() let userRecordArray = readLockParamsRecordOrFail(userAddressStr) let userNumStr = userRecordArray[IdxLockUserNum] @@ -732,7 +737,7 @@ func increaseLock(deltaDuration: Int) = { let b = gWxParamsResultList[1].ai() let period = gWxParamsResultList[2].ai().toString() - let currUserGwx = calcCurrentGwxAmount(userAddressStr) + let currUserGwx = getGwxAmount(userAddress) let gwxDiff = gWxAmountStart - currUserGwx if (gwxDiff < 0) then throwErr("gwxDiff is less then 0: " + gwxDiff.toString()) else let totalCachedGwxRaw = getGwxTotal() @@ -760,8 +765,10 @@ func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { } @Callable(i) -func unlock(userAddress: String) = { - let userRecordArray = readLockParamsRecordOrFail(userAddress) +func unlock(userAddressStr: String, amount: Int) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let userRecordArray = readLockParamsRecordOrFail(userAddressStr) let userNumStr = userRecordArray[IdxLockUserNum] let userAmount = userRecordArray[IdxLockAmount].parseIntValue() @@ -770,13 +777,11 @@ func unlock(userAddress: String) = { let lockEnd = lockStart + lockDuration # TODO: save claimed amount let userClaimed = 0 + # TODO: gwx amount + let gwxAmount = 0 - let blocksInDay = 24 * 60 let t = (height - lockStart) / blocksInDay - let daysInYearX8 = fraction(3652425, MULT8, 10000) - # let blocksInYearX8 = daysInYearX8 * blocksInDay - # let monthsInYear = 12 - # let blockInMonthX8 = blocksInYearX8 / monthsInYear + let exponent = fraction( t, 8 * blocksInDay * MULT8, @@ -792,10 +797,20 @@ func unlock(userAddress: String) = { ) } + if (amount > wxWithdrawable) + then throwErr("maximum amount to unlock: " + wxWithdrawable.toString()) + else + let gwxBurned = max([ - wxWithdrawable, + amount, fraction(t, userAmount * MULT8, 4 * daysInYearX8) ]) + let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") + let lockedGwxAmount = getLockedGwxAmount(userAddress) + + if (gwxRemaining < lockedGwxAmount) + then throwErr("locked gwx amount: " + lockedGwxAmount.toString()) + else let cfgArray = readConfigArrayOrFail() let assetId = cfgArray[IdxCfgAssetId].fromBase58String() @@ -804,17 +819,19 @@ func unlock(userAddress: String) = { let period = mathContract.getInteger(keyNextPeriod()).valueOrElse(0) - LockParamsEntry(userAddress, userNumStr, 0, lockStart, lockDuration, 0, 0, period.toString()) + LockParamsEntry(userAddressStr, userNumStr, 0, lockStart, lockDuration, 0, 0, period.toString()) ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed - :+ HistoryEntry("unlock", userAddress, userAmount, lockStart, lockDuration, 0, 0, i) - :+ ScriptTransfer(userAddress.addressFromStringValue(), userAmount, assetId) + :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, 0, 0, i) + :+ ScriptTransfer(userAddress, userAmount, assetId) } @Callable(i) -func gwxUserInfoREADONLY(userAddress: String) = { - let gwxAmount = calcCurrentGwxAmount(userAddress) +func gwxUserInfoREADONLY(userAddressStr: String) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let gwxAmount = getGwxAmount(userAddress) - ([], [gwxAmount]) + ([], [gwxAmount]) } # for lp_staking_pools @@ -837,9 +854,12 @@ func userMaxDurationREADONLY(userAddressStr: String) = { } } +# TODO: replace by getUserGwxAmountREADONLY (in all contracts) @Callable(i) -func getUserGwxAmountAtHeightREADONLY(userAddress: String, targetHeight: Int) = { - let gwxAmount = userAddress.calcUserGwxAmountAtHeight(targetHeight) +func getUserGwxAmountAtHeightREADONLY(userAddressStr: String) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let gwxAmount = userAddress.getGwxAmount() ([], gwxAmount) } From b6062d09ca1290c9952b558726cc1df839cbbd5b Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:27:09 +0500 Subject: [PATCH 03/86] getLockedGwxAmount draft update --- ride/boosting.ride | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 7380886f6..fe950537c 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -293,9 +293,21 @@ func getGwxTotal() = { this.getInteger(keyGwxTotal()).valueOrElse(0) } -# TODO: should returns locked gwx amount func getLockedGwxAmount(userAddress: Address) = { - 0 + # TODO: get locked amount for userAddress (invoke or state) + let lockedProposal = 0 + let lockedVotingEmissionRate = 0 + let lockedVotingEmission = 0 + let lockedVotingVerified = 0 + + let locked = max([ + lockedProposal, + lockedVotingEmissionRate, + lockedVotingEmission, + lockedVotingVerified + ]) + + locked } func HistoryEntry(type: String, user: String, amount: Int, lockStart: Int, duration: Int, k: Int, b: Int, i: Invocation) = { From bd8e3b81a49cb7a03fceddb791e96492af310532 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 14 Jul 2023 17:36:44 +0500 Subject: [PATCH 04/86] boosting_v2.ride --- ride/boosting.ride | 157 +++---- ride/boosting_v2.ride | 936 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 992 insertions(+), 101 deletions(-) create mode 100644 ride/boosting_v2.ride diff --git a/ride/boosting.ride b/ride/boosting.ride index fe950537c..ad440637f 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -13,15 +13,8 @@ let SEP = "__" let SCALE8 = 8 let MULT8 = 100000000 let POOLWEIGHTMULT = MULT8 -let contractFilename = "boosting.ride" -let blocksInDay = 24 * 60 -let daysInYearX8 = fraction(3652425, MULT8, 10000) -let blocksInYearX8 = daysInYearX8 * blocksInDay -let monthsInYear = 12 -let blockInMonthX8 = blocksInYearX8 / monthsInYear - -func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") +func wrapErr(msg: String) = ["boosting.ride:", msg].makeString(" ") func throwErr(msg: String) = msg.wrapErr().throw() # getStringOrFail @@ -50,10 +43,6 @@ func ai(val: Any) = { } } -func ensurePositive(v: Int, m: String|Unit) = { - if (v >= 0) then v else throwErr(m.valueOrElse("value") + " should be positive") -} - func keyReferralsContractAddress() = ["%s%s", "config", "referralsContractAddress"].makeString(SEP) let referralsContractAddressOrFail = this.strf(keyReferralsContractAddress()).addressFromStringValue() @@ -230,7 +219,8 @@ func keyTotalMaxBoostINTEGRAL() = "%s%s__maxBoostI func keyUserBoostAvalaibleToClaimTotal(userNum: String) = makeString(["%s%d__userBoostAvaliableToClaimTotal", userNum], SEP) func keyUserBoostClaimed(userNum: String) = makeString(["%s%d__userBoostClaimed", userNum], SEP) -func keyGwxTotal() = "%s%s__gwx__total" +func keyTotalCachedGwx() = "%s%s__gwxCached__total" +func keyTotalCachedGwxCorrective() = "%s__gwxCachedTotalCorrective" # Voting emission # User vote @@ -289,25 +279,20 @@ let votingEmissionContract = factoryContract.getStringValue(keyVotingEmissionCon let boostCoeff = emissionContract.invoke("getBoostCoeffREADONLY", [], []).exactAs[Int] -func getGwxTotal() = { - this.getInteger(keyGwxTotal()).valueOrElse(0) -} +func getTotalCachedGwx(correct: Boolean) = { + let currentEpochUi = votingEmissionContract.getIntegerValue(keyCurrentEpochUi()) + + let keyTargetEpoch = ["%s%s", "totalCachedGwxCorrection__activationEpoch"].makeString(SEP) + let targetEpochOption = this.getInteger(keyTargetEpoch) -func getLockedGwxAmount(userAddress: Address) = { - # TODO: get locked amount for userAddress (invoke or state) - let lockedProposal = 0 - let lockedVotingEmissionRate = 0 - let lockedVotingEmission = 0 - let lockedVotingVerified = 0 + let totalCachedGwxRaw = this.getInteger(keyTotalCachedGwx()).valueOrElse(0) - let locked = max([ - lockedProposal, - lockedVotingEmissionRate, - lockedVotingEmission, - lockedVotingVerified - ]) + let isCorrectionActivated = targetEpochOption.isDefined() && (currentEpochUi >= targetEpochOption.value()) + let corrective = if (isCorrectionActivated && correct) then { + this.getInteger(keyTotalCachedGwxCorrective()).valueOrElse(0) + } else 0 - locked + max([0, totalCachedGwxRaw + corrective]) } func HistoryEntry(type: String, user: String, amount: Int, lockStart: Int, duration: Int, k: Int, b: Int, i: Invocation) = { @@ -342,12 +327,10 @@ func StatsEntry(totalLockedInc: Int, durationInc: Int, lockCountInc: Int, usersC IntegerEntry(totalAmountKEY, totalAmount + totalLockedInc)] } -func calcGwxAmount(amount: Int, duration: Int) = { - fraction( - amount, - duration * MULT8, - 4 * blocksInYearX8 - ) +# TODO MOVE INTO MATH CONTRACT +func calcGwxAmount(kRaw: Int, bRaw: Int, h: Int) = { + let SCALE = 1000 # see math contract + (kRaw * h + bRaw) / SCALE } func LockParamsEntry(userAddress: String, userNum: String, amount: Int, start: Int, duration: Int, k: Int, b: Int, period: String) = { @@ -360,7 +343,8 @@ func LockParamsEntry(userAddress: String, userNum: String, amount: Int, start: I let kByPeriodKEY = keyLockParamByPeriodK(userNum, period) let bByPeriodKEY = keyLockParamByPeriodB(userNum, period) - let gwxAmount = calcGwxAmount(amount, duration) + # TODO think about moving to another place + let gwxAmount = calcGwxAmount(k, b, height) [IntegerEntry(userAmountKEY, amount), IntegerEntry(startBlockKEY, start), IntegerEntry(durationKEY, duration), @@ -381,9 +365,22 @@ func extractOptionalPaymentAmountOrFail(i: Invocation, expectedAssetId: ByteVect pmt.amount } -# TODO: parse state and return user's current gwx amount -func getGwxAmount(userAddress: Address) = { - 0 +func calcUserGwxAmountAtHeight(userAddress: String, targetHeight: Int) = { + let EMPTY = "empty" + let user2NumMappingKEY = keyUser2NumMapping(userAddress) + let userNum = user2NumMappingKEY.getString().valueOrElse(EMPTY) + + let k = keyLockParamK(userNum).getInteger().valueOrElse(0) + let b = keyLockParamB(userNum).getInteger().valueOrElse(0) + + let gwxAmountCalc = calcGwxAmount(k, b, targetHeight) + let gwxAmount = if (gwxAmountCalc < 0 ) then 0 else gwxAmountCalc + + gwxAmount +} + +func calcCurrentGwxAmount(userAddress: String) = { + userAddress.calcUserGwxAmountAtHeight(height) } func getVotingEmissionEpochInfo() = { @@ -656,7 +653,7 @@ func lockActions(i: Invocation, duration: Int) = { let b = gWxParamsResultList[1].ai() let period = gWxParamsResultList[2].ai().toString() - let totalCachedGwxRaw = getGwxTotal() + let totalCachedGwxRaw = getTotalCachedGwx(false) let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNumStr) let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 @@ -671,7 +668,7 @@ func lockActions(i: Invocation, duration: Int) = { :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, duration, k, b, i) ++ [ IntegerEntry(userBoostEmissionLastIntegralKEY, boostEmissionIntegral), - IntegerEntry(keyGwxTotal(), totalCachedGwxRaw + gWxAmountStart) + IntegerEntry(keyTotalCachedGwx(), totalCachedGwxRaw + gWxAmountStart) ], gWxAmountStart) } @@ -718,8 +715,7 @@ func increaseLock(deltaDuration: Int) = { let pmtAmount = extractOptionalPaymentAmountOrFail(i, assetId) - let userAddress = i.caller - let userAddressStr = userAddress.toString() + let userAddressStr = i.caller.toString() let userRecordArray = readLockParamsRecordOrFail(userAddressStr) let userNumStr = userRecordArray[IdxLockUserNum] @@ -749,16 +745,17 @@ func increaseLock(deltaDuration: Int) = { let b = gWxParamsResultList[1].ai() let period = gWxParamsResultList[2].ai().toString() - let currUserGwx = getGwxAmount(userAddress) + let currUserGwx = calcCurrentGwxAmount(userAddressStr) let gwxDiff = gWxAmountStart - currUserGwx if (gwxDiff < 0) then throwErr("gwxDiff is less then 0: " + gwxDiff.toString()) else - let totalCachedGwxRaw = getGwxTotal() + let totalCachedGwxRaw = getTotalCachedGwx(false) + let totalCachedGwxCorrected = getTotalCachedGwx(true) LockParamsEntry(userAddressStr, userNumStr, userAmountNew, lockStartNew, lockDurationNew, k, b, period) ++ StatsEntry(pmtAmount, deltaDuration, 0, 0) :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, lockDurationNew, k, b, i) ++ [ - IntegerEntry(keyGwxTotal(), totalCachedGwxRaw + gwxDiff) + IntegerEntry(keyTotalCachedGwx(), totalCachedGwxRaw + gwxDiff) ] } @@ -777,73 +774,34 @@ func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { } @Callable(i) -func unlock(userAddressStr: String, amount: Int) = { - let userAddress = userAddressStr.addressFromString() - .valueOrErrorMessage(wrapErr("invalid user address")) - let userRecordArray = readLockParamsRecordOrFail(userAddressStr) +func unlock(userAddress: String) = { + let userRecordArray = readLockParamsRecordOrFail(userAddress) let userNumStr = userRecordArray[IdxLockUserNum] let userAmount = userRecordArray[IdxLockAmount].parseIntValue() let lockStart = userRecordArray[IdxLockStart].parseIntValue() let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() let lockEnd = lockStart + lockDuration - # TODO: save claimed amount - let userClaimed = 0 - # TODO: gwx amount - let gwxAmount = 0 - - let t = (height - lockStart) / blocksInDay - - let exponent = fraction( - t, - 8 * blocksInDay * MULT8, - lockDuration - ) - let wxWithdrawable = if (height > lockEnd) then { - userAmount - userClaimed - } else { - fraction( - userAmount, - MULT8 - pow(5, 1, exponent, SCALE8, SCALE8, DOWN), - MULT8 - ) - } - - if (amount > wxWithdrawable) - then throwErr("maximum amount to unlock: " + wxWithdrawable.toString()) - else - - let gwxBurned = max([ - amount, - fraction(t, userAmount * MULT8, 4 * daysInYearX8) - ]) - let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") - let lockedGwxAmount = getLockedGwxAmount(userAddress) - - if (gwxRemaining < lockedGwxAmount) - then throwErr("locked gwx amount: " + lockedGwxAmount.toString()) - else let cfgArray = readConfigArrayOrFail() let assetId = cfgArray[IdxCfgAssetId].fromBase58String() + if (lockEnd >= height) then throwErr("wait " + lockEnd.toString() + " to unlock") else if (userAmount <= 0) then throwErr("nothing to unlock") else let period = mathContract.getInteger(keyNextPeriod()).valueOrElse(0) - LockParamsEntry(userAddressStr, userNumStr, 0, lockStart, lockDuration, 0, 0, period.toString()) + LockParamsEntry(userAddress, userNumStr, 0, lockStart, lockDuration, 0, 0, period.toString()) ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed - :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, 0, 0, i) - :+ ScriptTransfer(userAddress, userAmount, assetId) + :+ HistoryEntry("unlock", userAddress, userAmount, lockStart, lockDuration, 0, 0, i) + :+ ScriptTransfer(userAddress.addressFromStringValue(), userAmount, assetId) } @Callable(i) -func gwxUserInfoREADONLY(userAddressStr: String) = { - let userAddress = userAddressStr.addressFromString() - .valueOrErrorMessage(wrapErr("invalid user address")) - let gwxAmount = getGwxAmount(userAddress) +func gwxUserInfoREADONLY(userAddress: String) = { + let gwxAmount = calcCurrentGwxAmount(userAddress) - ([], [gwxAmount]) + ([], [gwxAmount]) } # for lp_staking_pools @@ -866,19 +824,16 @@ func userMaxDurationREADONLY(userAddressStr: String) = { } } -# TODO: replace by getUserGwxAmountREADONLY (in all contracts) @Callable(i) -func getUserGwxAmountAtHeightREADONLY(userAddressStr: String) = { - let userAddress = userAddressStr.addressFromString() - .valueOrErrorMessage(wrapErr("invalid user address")) - let gwxAmount = userAddress.getGwxAmount() +func getUserGwxAmountAtHeightREADONLY(userAddress: String, targetHeight: Int) = { + let gwxAmount = userAddress.calcUserGwxAmountAtHeight(targetHeight) ([], gwxAmount) } @Callable(i) -func getGwxTotalREADONLY() = { - ([], getGwxTotal()) +func getTotalCachedGwxREADONLY() = { + ([], getTotalCachedGwx(true)) } # call this function when wxEmissionRate or boostCoeff changes diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride new file mode 100644 index 000000000..fe950537c --- /dev/null +++ b/ride/boosting_v2.ride @@ -0,0 +1,936 @@ +{-# STDLIB_VERSION 6 #-} +{-# CONTENT_TYPE DAPP #-} +{-# SCRIPT_TYPE ACCOUNT #-} + +# Required state entries: +# * "%s%s__config__referralsContractAddress": String +# * "%s__nextUserNum": Int +# * "%s%s__config__factoryAddress": String +# * "%s__config": String ("%s%d%d%d________") +# * "%s__lpStakingPoolsContract": String + +let SEP = "__" +let SCALE8 = 8 +let MULT8 = 100000000 +let POOLWEIGHTMULT = MULT8 +let contractFilename = "boosting.ride" + +let blocksInDay = 24 * 60 +let daysInYearX8 = fraction(3652425, MULT8, 10000) +let blocksInYearX8 = daysInYearX8 * blocksInDay +let monthsInYear = 12 +let blockInMonthX8 = blocksInYearX8 / monthsInYear + +func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") +func throwErr(msg: String) = msg.wrapErr().throw() + +# getStringOrFail +func strf(address: Address, key: String) = address.getString(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) +# getIntOrZero +func ioz(address: Address, key: String) = address.getInteger(key).valueOrElse(0) +# getIntOrDefault +func iod(address: Address, key: String, defaultVal: Int) = address.getInteger(key).valueOrElse(defaultVal) +# getIntOrFail +func iof(address: Address, key: String) = address.getInteger(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) +func abs(val: Int) = if (val < 0) then -val else val + +# asAnyList +func aal(val: Any) = { + match val { + case valAnyLyst: List[Any] => valAnyLyst + case _ => throwErr("fail to cast into List[Any]") + } +} + +# asInt +func ai(val: Any) = { + match val { + case valInt: Int => valInt + case _ => throwErr("fail to cast into Int") + } +} + +func ensurePositive(v: Int, m: String|Unit) = { + if (v >= 0) then v else throwErr(m.valueOrElse("value") + " should be positive") +} + +func keyReferralsContractAddress() = ["%s%s", "config", "referralsContractAddress"].makeString(SEP) +let referralsContractAddressOrFail = this.strf(keyReferralsContractAddress()).addressFromStringValue() + +let keyReferralProgramName = ["%s%s", "referral", "programName"].makeString(SEP) +let referralProgramNameDefault = "wxlock" +let referralProgramName = this.getString(keyReferralProgramName).valueOrElse(referralProgramNameDefault) + +# FACTORY API +# own factory address key +func keyFactoryAddress() = "%s%s__config__factoryAddress" + +let IdxFactoryCfgStakingDapp = 1 +let IdxFactoryCfgBoostingDapp = 2 +let IdxFactoryCfgIdoDapp = 3 +let IdxFactoryCfgTeamDapp = 4 +let IdxFactoryCfgEmissionDapp = 5 +let IdxFactoryCfgRestDapp = 6 +let IdxFactoryCfgSlippageDapp = 7 +let IdxFactoryCfgDaoDapp = 8 +let IdxFactoryCfgMarketingDapp = 9 +let IdxFactoryCfgGwxRewardDapp = 10 +let IdxFactoryCfgBirdsDapp = 11 + +func keyFactoryCfg() = "%s__factoryConfig" +func keyFactoryLpList() = "%s__lpTokensList" # not used anymore +func keyFactoryLpAssetToPoolContractAddress(lpAssetStr: String) = makeString(["%s%s%s", lpAssetStr, "mappings__lpAsset2PoolContract"], SEP) +func keyFactoryPoolWeight(contractAddress: String) = { ["%s%s", "poolWeight", contractAddress].makeString(SEP) } +func keyFactoryPoolWeightHistory(poolAddress: String, num: Int) = {"%s%s__poolWeight__" + poolAddress + "__" + num.toString()} + +func readFactoryAddressOrFail() = this.strf(keyFactoryAddress()).addressFromStringValue() +func readLpList() = readFactoryAddressOrFail().getString(keyFactoryLpList()).valueOrElse("").split(SEP) +func readFactoryCfgOrFail(factory: Address) = factory.strf(keyFactoryCfg()).split(SEP) +func getBoostingAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgBoostingDapp].addressFromStringValue() +func getEmissionAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgEmissionDapp].addressFromStringValue() +func getStakingAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgStakingDapp].addressFromStringValue() +func getGwxRewardAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgGwxRewardDapp].addressFromStringValue() + +func keyManagerPublicKey() = "%s__managerPublicKey" +func keyManagerVaultAddress() = "%s__managerVaultAddress" + +# EMISSION API +func keyEmissionRatePerBlockCurrent() = "%s%s__ratePerBlock__current" +func keyEmissionRatePerBlockMaxCurrent() = "%s%s__ratePerBlockMax__current" +func keyEmissionStartBlock() = "%s%s__emission__startBlock" +func keyBoostingV2LastUpdateHeight() = "%s%s__boostingV2__startBlock" +func keyBoostingV2Integral() = "%s%s__boostingV2__integral" +func keyEmissionDurationInBlocks() = "%s%s__emission__duration" +func keyEmissionEndBlock() = "%s%s__emission__endBlock" + +# GWX REWARD (MATH) API +func keyNextPeriod() = "%s__nextPeriod" +func keyGwxRewardEmissionStartHeight() = "%s%s__gwxRewardEmissionPart__startHeight" + +# OWN KEYS +let IdxCfgAssetId = 1 +let IdxCfgMinLockAmount = 2 +let IdxCfgMinLockDuration = 3 +let IdxCfgMaxLockDuration = 4 +let IdxCfgMathContract = 5 + +func keyConfig() = {"%s__config"} +func readConfigArrayOrFail() = this.strf(keyConfig()).split(SEP) +let mathContract = readConfigArrayOrFail()[IdxCfgMathContract].addressFromStringValue() + +func formatConfigS(assetId: String, minLockAmount: String, minLockDuration: String, maxLockDuration: String, mathContract: String) = { + makeString([ + "%s%d%d%d", + assetId, # 1 + minLockAmount, # 2 + minLockDuration, # 3 + maxLockDuration, # 4 + mathContract # 5 + ], + SEP) +} + +func formatConfig(assetId: String, minLockAmount: Int, minLockDuration: Int, maxLockDuration: Int, mathContract: String) = { + formatConfigS( + assetId, # 1 + minLockAmount.toString(), # 2 + minLockDuration.toString(), # 3 + maxLockDuration.toString(), # 4 + mathContract # 5 + ) +} + +func getManagerVaultAddressOrThis() = { + match keyManagerVaultAddress().getString() { + case s:String => s.addressFromStringValue() + case _=> this + } +} + +func managerPublicKeyOrUnit() = { + let managerVaultAddress = getManagerVaultAddressOrThis() + match managerVaultAddress.getString(keyManagerPublicKey()) { + case s: String => s.fromBase58String() + case _: Unit => unit + } +} + +func mustManager(i: Invocation) = { + let pd = "Permission denied".throwErr() + + match managerPublicKeyOrUnit() { + case pk: ByteVector => i.callerPublicKey == pk || pd + case _: Unit => i.caller == this || pd + } +} + +let IdxLockUserNum = 1 +let IdxLockAmount = 2 +let IdxLockStart = 3 +let IdxLockDuration = 4 +let IdxLockParamK = 5 +let IdxLockParamB = 6 + +func keyLockParamsRecord(userAddress: String) = makeString(["%s%s__lock", userAddress], SEP) +func readLockParamsRecordOrFail(userAddress: String) = this.strf(keyLockParamsRecord(userAddress)).split(SEP) + +func formatLockParamsRecordS(userNum: String, amount: String, start: String, duration: String, paramK: String, paramB: String, lastUpdTimestamp: String, gwxAmount: String) = { + + makeString([ + "%d%d%d%d%d%d%d%d", # 0 + userNum, # 1 + amount, # 2 + start, # 3 + duration, # 4 + paramK, # 5 + paramB, # 6 + lastUpdTimestamp, #7 + gwxAmount # 8 + ], + SEP) +} + +func formatLockParamsRecord(userNum: String, amount: Int, start: Int, duration: Int, paramK: Int, paramB: Int, gwxAmount: Int) = { + formatLockParamsRecordS( + userNum, # 1 + amount.toString(), # 2 + start.toString(), # 3 + duration.toString(), # 4 + paramK.toString(), # 5 + paramB.toString(), # 6 + lastBlock.timestamp.toString(), # 7 + gwxAmount.toString() # 8 + ) +} + +# mappings +func keyNextUserNum() = "%s__nextUserNum" +func keyUser2NumMapping(userAddress: String) = makeString(["%s%s%s__mapping__user2num", userAddress], SEP) +func keyNum2UserMapping(num: String) = makeString(["%s%s%s__mapping__num2user", num], SEP) + +func keyLockParamUserAmount(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "amount"], SEP) +func keyLockParamStartBlock(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "start"], SEP) +func keyLockParamDuration(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "duration"], SEP) +func keyLockParamK(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "k"], SEP) +func keyLockParamB(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "b"], SEP) +func keyLockParamByPeriodK(userNum: String, period: String) = makeString(["%s%d%s%d__paramByPeriod", userNum, "k", period], SEP) +func keyLockParamByPeriodB(userNum: String, period: String) = makeString(["%s%d%s%d__paramByPeriod", userNum, "b", period], SEP) + +# TODO - consider to concat +func keyLockParamTotalAmount() = "%s%s__stats__activeTotalLocked" +func keyStatsLocksDurationSumInBlocks() = "%s%s__stats__locksDurationSumInBlocks" +func keyStatsLocksCount() = "%s%s__stats__locksCount" +func keyStatsUsersCount() = "%s%s__stats__activeUsersCount" + +# boost integral +func keyUserBoostEmissionLastINTEGRAL(userNum: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum], SEP) +func keyUserLpBoostEmissionLastINTEGRAL(userNum: String, lpAssetId: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum, lpAssetId], SEP) +func keyUserMaxBoostINTEGRAL(userNum: String) = makeString(["%s%d__maxBoostInt", userNum], SEP) +func keyTotalMaxBoostINTEGRAL() = "%s%s__maxBoostInt__total" +func keyUserBoostAvalaibleToClaimTotal(userNum: String) = makeString(["%s%d__userBoostAvaliableToClaimTotal", userNum], SEP) +func keyUserBoostClaimed(userNum: String) = makeString(["%s%d__userBoostClaimed", userNum], SEP) + +func keyGwxTotal() = "%s%s__gwx__total" + +# Voting emission +# User vote +func keyVote(amountAssetId: String, priceAssetId: String, address: Address, epoch: Int) = { + ["%s%s%s%s%d", "vote", amountAssetId, priceAssetId, address.toString(), epoch.toString()].makeString(SEP) +} +func keyStartHeightByEpoch(epoch: Int) = ["%s%d", "startHeight", epoch.toString()].makeString(SEP) +func keyCurrentEpochUi() = ["%s", "currentEpochUi"].makeString(SEP) + +# Initial value is stored on voting emission account +# Following values are stored on this account +func keyVotingResultStaked(lpAssetIdStr: String, epoch: Int) = { + ["%s%s%d", "votingResultStaked", lpAssetIdStr, epoch.toString()].makeString(SEP) +} + +# this +# We need to save integral to handle changing value +func keyVotingResultStakedIntegral(lpAssetIdStr: String, epoch: Int) = { + ["%s%s%d", "votingResultStakedIntegral", lpAssetIdStr, epoch.toString()].makeString(SEP) +} +# Height at which voting result staked was last changed +func keyVotingResultStakedLastUpdateHeight(lpAssetIdStr: String, epoch: Int) = { + ["%s%s%d", "votingResultStakedIntegralLastUpdateHeight", lpAssetIdStr, epoch.toString()].makeString(SEP) +} +# Last integral value used by user +# This value is needed to calculate unclaimed boost +func keyVotingResultStakedIntegralLast(lpAssetIdStr: String, address: Address, epoch: Int) = { + ["%s%s%s%d", "votingResultStakedIntegralLast", lpAssetIdStr, address.toString(), epoch.toString()].makeString(SEP) +} +func keyVoteStakedIntegral(lpAssetIdStr: String, address: Address, epoch: Int) = { + ["%s%s%s%d", "voteStakedIntegral", lpAssetIdStr, address.toString(), epoch.toString()].makeString(SEP) +} +func keyVoteStakedLastUpdateHeight(lpAssetIdStr: String, address: Address, epoch: Int) = { + ["%s%s%s%d", "voteStakedIntegralLastUpdateHeight", lpAssetIdStr, address.toString(), epoch.toString()].makeString(SEP) +} +func keyVoteStakedIntegralLast(lpAssetIdStr: String, address: Address, epoch: Int) = { + ["%s%s%s%d", "voteStakedIntegralLast", lpAssetIdStr, address.toString(), epoch.toString()].makeString(SEP) +} + +# staking +func keyStakedByUser(userAddressStr: String, lpAssetIdStr: String) = ["%s%s%s", "staked", userAddressStr, lpAssetIdStr].makeString(SEP) + +# GLOBAL VARIABLES +# CONSTRUCTOR IS NOT FAILED BECAUSE GLOBAL VARIABLES ARE NOT USED +let factoryContract = readFactoryAddressOrFail() +let factoryCfg = factoryContract.readFactoryCfgOrFail() +let emissionContract = factoryCfg.getEmissionAddressOrFail() +let stakingContract = factoryCfg.getStakingAddressOrFail() +let gwxRewardContract = factoryCfg.getGwxRewardAddressOrFail() +let lpStakingPoolsContract = ["%s", "lpStakingPoolsContract"].makeString(SEP) + .getString().valueOrErrorMessage("lp_staking_pools contract address is undefined".wrapErr()) + .addressFromString().valueOrErrorMessage("invalid lp_staking_pools contract address".wrapErr()) + +let keyVotingEmissionContract = ["%s", "votingEmissionContract"].makeString(SEP) +let votingEmissionContract = factoryContract.getStringValue(keyVotingEmissionContract).addressFromStringValue() + +let boostCoeff = emissionContract.invoke("getBoostCoeffREADONLY", [], []).exactAs[Int] + +func getGwxTotal() = { + this.getInteger(keyGwxTotal()).valueOrElse(0) +} + +func getLockedGwxAmount(userAddress: Address) = { + # TODO: get locked amount for userAddress (invoke or state) + let lockedProposal = 0 + let lockedVotingEmissionRate = 0 + let lockedVotingEmission = 0 + let lockedVotingVerified = 0 + + let locked = max([ + lockedProposal, + lockedVotingEmissionRate, + lockedVotingEmission, + lockedVotingVerified + ]) + + locked +} + +func HistoryEntry(type: String, user: String, amount: Int, lockStart: Int, duration: Int, k: Int, b: Int, i: Invocation) = { + let historyKEY = makeString(["%s%s%s%s__history", type, user, i.transactionId.toBase58String()], SEP) + let historyDATA = makeString([ + "%d%d%d%d%d%d%d", + lastBlock.height.toString(), + lastBlock.timestamp.toString(), + amount.toString(), + lockStart.toString(), + duration.toString(), + k.toString(), + b.toString()], + SEP) + StringEntry(historyKEY, historyDATA) +} + +func StatsEntry(totalLockedInc: Int, durationInc: Int, lockCountInc: Int, usersCountInc: Int) = { + let locksDurationSumInBlocksKEY = keyStatsLocksDurationSumInBlocks() + let locksCountKEY = keyStatsLocksCount() + let usersCountKEY = keyStatsUsersCount() + let totalAmountKEY = keyLockParamTotalAmount() + + let locksDurationSumInBlocks = this.ioz(locksDurationSumInBlocksKEY) + let locksCount = this.ioz(locksCountKEY) + let usersCount = this.ioz(usersCountKEY) + let totalAmount = this.ioz(totalAmountKEY) + + [IntegerEntry(locksDurationSumInBlocksKEY, locksDurationSumInBlocks + durationInc), + IntegerEntry(locksCountKEY, locksCount + lockCountInc), + IntegerEntry(usersCountKEY, usersCount + usersCountInc), + IntegerEntry(totalAmountKEY, totalAmount + totalLockedInc)] +} + +func calcGwxAmount(amount: Int, duration: Int) = { + fraction( + amount, + duration * MULT8, + 4 * blocksInYearX8 + ) +} + +func LockParamsEntry(userAddress: String, userNum: String, amount: Int, start: Int, duration: Int, k: Int, b: Int, period: String) = { + + let userAmountKEY = keyLockParamUserAmount(userNum) + let startBlockKEY = keyLockParamStartBlock(userNum) + let durationKEY = keyLockParamDuration(userNum) + let kKEY = keyLockParamK(userNum) + let bKEY = keyLockParamB(userNum) + let kByPeriodKEY = keyLockParamByPeriodK(userNum, period) + let bByPeriodKEY = keyLockParamByPeriodB(userNum, period) + + let gwxAmount = calcGwxAmount(amount, duration) + [IntegerEntry(userAmountKEY, amount), + IntegerEntry(startBlockKEY, start), + IntegerEntry(durationKEY, duration), + IntegerEntry(kKEY, k), + IntegerEntry(bKEY, b), + IntegerEntry(kByPeriodKEY, k), + IntegerEntry(bByPeriodKEY, b), + StringEntry( # fixme: it does not work for multi lock. The key should include period + keyLockParamsRecord(userAddress), + formatLockParamsRecord(userNum, amount, start, duration, k, b, gwxAmount))] +} + +func extractOptionalPaymentAmountOrFail(i: Invocation, expectedAssetId: ByteVector) = { + if (i.payments.size() > 1) then throwErr("only one payment is allowed") else + if (i.payments.size() == 0) then 0 else + let pmt = i.payments[0] + if (pmt.assetId.value() != expectedAssetId) then throwErr("invalid asset id in payment") else + pmt.amount +} + +# TODO: parse state and return user's current gwx amount +func getGwxAmount(userAddress: Address) = { + 0 +} + +func getVotingEmissionEpochInfo() = { + let (currentEpochUi, lastFinalizedEpoch) = { + let currentEpochUi = votingEmissionContract.getInteger(keyCurrentEpochUi()).value() + let lastFinalizedEpoch = currentEpochUi - 1 + if (lastFinalizedEpoch < 0) then "invalid epoch".throwErr() else (currentEpochUi, lastFinalizedEpoch) + } + + let currentEpochStartHeight = votingEmissionContract.getInteger(currentEpochUi.keyStartHeightByEpoch()).value() + + (lastFinalizedEpoch, currentEpochStartHeight) +} + +func getPoolAssetsByLpAssetId(lpAssetIdStr: String) = { + let idxAmountAssetId = 4 + let idxPriceAssetId = 5 + let poolCfg = factoryContract.invoke("getPoolConfigByLpAssetIdREADONLY", [lpAssetIdStr], []).exactAs[List[Any]] + let amountAssetId = poolCfg[idxAmountAssetId].exactAs[String] + let priceAssetId = poolCfg[idxPriceAssetId].exactAs[String] + + (amountAssetId, priceAssetId) +} + +func getUserVoteFinalized(lpAssetIdStr: String, userAddressStr: String) = { + let userAddress = userAddressStr.addressFromStringValue() + let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() + let (amountAssetId, priceAssetId) = lpAssetIdStr.getPoolAssetsByLpAssetId() + let userVoteKey = keyVote(amountAssetId, priceAssetId, userAddress, lastFinalizedEpoch) + let userVote = votingEmissionContract.getInteger(userVoteKey).valueOrElse(0) + + userVote +} + +func getUserVoteStaked(lpAssetIdStr: String, userAddressStr: String) = { + let stakedByUser = stakingContract.getInteger(keyStakedByUser(userAddressStr, lpAssetIdStr)).valueOrElse(0) + let userVote = lpAssetIdStr.getUserVoteFinalized(userAddressStr) + + if (stakedByUser == 0) then 0 else userVote +} + +func getVotingResultStaked(lpAssetIdStr: String) = { + let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() + let votingResultStakedStart = votingEmissionContract.getInteger(lpAssetIdStr.keyVotingResultStaked(lastFinalizedEpoch)).valueOrElse(0) + let votingResultStaked = this.getInteger(lpAssetIdStr.keyVotingResultStaked(lastFinalizedEpoch)).valueOrElse(votingResultStakedStart) + + votingResultStaked +} + +func getVotingResultStakedIntegral(lpAssetIdStr: String) = { + let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() + let votingResultStaked = lpAssetIdStr.getVotingResultStaked() + let votingResultStakedIntegralPrev = this.getInteger( + lpAssetIdStr.keyVotingResultStakedIntegral(lastFinalizedEpoch) + ).valueOrElse(0) + let votingResultStakedLastUpdateHeight = this.getInteger( + lpAssetIdStr.keyVotingResultStakedLastUpdateHeight(lastFinalizedEpoch) + ).valueOrElse(currentEpochStartHeight) + let votingResultStakedIntegralDh = height - votingResultStakedLastUpdateHeight + let votingResultStakedIntegral = votingResultStakedIntegralDh * votingResultStaked + votingResultStakedIntegralPrev + + votingResultStakedIntegral +} + +# Voting result staked is changing only when staked vote is changing +# So this function is called in refreshVoteStakedIntegral +func refreshVotingResultStakedIntegral( + lpAssetIdStr: String, + stakedVoteDelta: Int +) = { + let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() + let votingResultStaked = lpAssetIdStr.getVotingResultStaked() + let votingResultStakedNew = votingResultStaked + stakedVoteDelta + let votingResultStakedIntegral = lpAssetIdStr.getVotingResultStakedIntegral() + + [ + IntegerEntry(lpAssetIdStr.keyVotingResultStaked(lastFinalizedEpoch), votingResultStakedNew), + IntegerEntry(lpAssetIdStr.keyVotingResultStakedLastUpdateHeight(lastFinalizedEpoch), height), + IntegerEntry(lpAssetIdStr.keyVotingResultStakedIntegral(lastFinalizedEpoch), votingResultStakedIntegral) + ] +} + +func getUserVoteStakedIntegral(lpAssetIdStr: String, userAddressStr: String) = { + let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() + let userAddress = userAddressStr.addressFromStringValue() + let userVoteStaked = lpAssetIdStr.getUserVoteStaked(userAddressStr) + let userVoteStakedIntegralPrev = this.getInteger( + lpAssetIdStr.keyVoteStakedIntegral(userAddress, lastFinalizedEpoch) + ).valueOrElse(0) + let userVoteStakedLastUpdateHeight = this.getInteger( + lpAssetIdStr.keyVoteStakedLastUpdateHeight(userAddress, lastFinalizedEpoch) + ).valueOrElse(currentEpochStartHeight) + let userVoteStakedIntegralDh = height - userVoteStakedLastUpdateHeight + let userVoteStakedIntegral = userVoteStakedIntegralDh * userVoteStaked + userVoteStakedIntegralPrev + + userVoteStakedIntegral +} + +func refreshVoteStakedIntegral( + lpAssetIdStr: String, + userAddressStr: String, + edge: Boolean # true: add, false: remove +) = { + let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() + let userAddress = userAddressStr.addressFromStringValue() + + let userVoteFinalized = lpAssetIdStr.getUserVoteFinalized(userAddressStr) + + let actions = if (userVoteFinalized == 0) then [] else { + let stakedVoteDelta = if (edge) then userVoteFinalized else -userVoteFinalized + let votingResultActions = lpAssetIdStr.refreshVotingResultStakedIntegral(stakedVoteDelta) + + let userVoteStakedIntegral = lpAssetIdStr.getUserVoteStakedIntegral(userAddressStr) + let voteActions = [ + IntegerEntry(lpAssetIdStr.keyVoteStakedLastUpdateHeight(userAddress, lastFinalizedEpoch), height), + IntegerEntry(lpAssetIdStr.keyVoteStakedIntegral(userAddress, lastFinalizedEpoch), userVoteStakedIntegral) + ] + + votingResultActions ++ voteActions + } + + actions +} + +func getStakedVotesIntegralsDiff(lpAssetIdStr: String, userAddressStr: String) = { + let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() + let userAddress = userAddressStr.addressFromStringValue() + + let userVoteStakedIntegralLastKey = lpAssetIdStr.keyVoteStakedIntegralLast(userAddress, lastFinalizedEpoch) + let userVoteStakedIntegralLast = this.getInteger(userVoteStakedIntegralLastKey).valueOrElse(0) + let votingResultStakedIntegralLastKey = lpAssetIdStr.keyVotingResultStakedIntegralLast(userAddress, lastFinalizedEpoch) + let votingResultStakedIntegralLast = this.getInteger(votingResultStakedIntegralLastKey).valueOrElse(0) + let userVoteStakedIntegral = lpAssetIdStr.getUserVoteStakedIntegral(userAddressStr) + let votingResultStakedIntegral = lpAssetIdStr.getVotingResultStakedIntegral() + let userVoteStakedIntegralDiff = userVoteStakedIntegral - userVoteStakedIntegralLast + let votingResultStakedIntegralDiff = votingResultStakedIntegral - votingResultStakedIntegralLast + + ( + [ + IntegerEntry(userVoteStakedIntegralLastKey, userVoteStakedIntegral), + IntegerEntry(votingResultStakedIntegralLastKey, votingResultStakedIntegral) + ], + userVoteStakedIntegralDiff, + votingResultStakedIntegralDiff + ) +} + +# Actions should be applied if wxEmissionPerBlock or boostCoeff changes +func refreshBoostEmissionIntegral() = { + let wxEmissionPerBlock = emissionContract.iof(keyEmissionRatePerBlockCurrent()) + let boostingV2LastUpdateHeightOption = this.getInteger(keyBoostingV2LastUpdateHeight()) + let boostingV2IngergalOption = this.getInteger(keyBoostingV2Integral()) + let emissionEnd = emissionContract.iof(keyEmissionEndBlock()) + let h = if (height > emissionEnd) then emissionEnd else height + + let dh = match boostingV2LastUpdateHeightOption { + case lastUpdateHeight: Int => max([h - lastUpdateHeight, 0]) + case _: Unit => 0 + } + + let boostEmissionPerBlock = wxEmissionPerBlock * (boostCoeff - 1) / boostCoeff + let boostEmissionIntegralPrev = boostingV2IngergalOption.valueOrElse(0) + let boostEmissionIntegral = boostEmissionPerBlock * dh + boostEmissionIntegralPrev + + ( + [ + IntegerEntry(keyBoostingV2Integral(), boostEmissionIntegral), + IntegerEntry(keyBoostingV2LastUpdateHeight(), height) + ], + boostEmissionIntegral + ) +} + +func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly: Boolean) = { + let userRecordOption = this.getString(keyLockParamsRecord(userAddressStr)) + if (userRecordOption == unit) then (0, [], "userRecord::is::empty") else + + let userRecordArray = userRecordOption.value().split(SEP) + let userNumStr = userRecordArray[IdxLockUserNum] + + let EMPTYSTR = "empty" # is used from REST + let poolWeight = if (lpAssetIdStr != EMPTYSTR) then { + let poolAddressStr = factoryContract.getString(keyFactoryLpAssetToPoolContractAddress(lpAssetIdStr)).valueOrErrorMessage(("unsupported lp asset " + lpAssetIdStr).wrapErr()) + factoryContract.getIntegerValue(poolAddressStr.keyFactoryPoolWeight()) + } else { + if (readOnly) then 0 else throwErr("not readonly mode: unsupported lp asset " + lpAssetIdStr) + } + + # BOOST INTEGRAL RECALC + # updated by claim + let userLpBoostEmissionLastIntegralKEY = keyUserLpBoostEmissionLastINTEGRAL(userNumStr, lpAssetIdStr) + # updated by lock + let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNumStr) + + let userBoostEmissionLastIntegral = this.getInteger(userLpBoostEmissionLastIntegralKEY) + .valueOrElse(this.ioz(userBoostEmissionLastIntegralKEY)) + + let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 + let userBoostEmissionIntegral = boostEmissionIntegral - userBoostEmissionLastIntegral + + if (userBoostEmissionIntegral < 0) then throwErr("wrong calculations") else + + let (stakedVotesIntegralsActions, userVoteIntegralDiff, totalVotesIntegralDiff) = getStakedVotesIntegralsDiff(lpAssetIdStr, userAddressStr) + + let poolUserBoostEmissionIntegral = fraction(userBoostEmissionIntegral, poolWeight, POOLWEIGHTMULT) + + let userBoostAvaliableToClaimTotalNew = if (totalVotesIntegralDiff == 0) then 0 else { + fraction(poolUserBoostEmissionIntegral, userVoteIntegralDiff, totalVotesIntegralDiff) + } + + # BOOST INTEGRAL + let dataState = [ + IntegerEntry(userLpBoostEmissionLastIntegralKEY, boostEmissionIntegral) + ] ++ stakedVotesIntegralsActions + + let debug = [ + userBoostEmissionLastIntegral.toString(), + userBoostEmissionIntegral.toString(), + poolWeight.toString(), + userVoteIntegralDiff.toString(), + totalVotesIntegralDiff.toString() + ].makeString(":") + + (userBoostAvaliableToClaimTotalNew, dataState, debug) +} + +func lockActions(i: Invocation, duration: Int) = { + let cfgArray = readConfigArrayOrFail() + let assetIdStr = cfgArray[IdxCfgAssetId] + let assetId = assetIdStr.fromBase58String() + let minLockAmount = cfgArray[IdxCfgMinLockAmount].parseIntValue() + let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseIntValue() + let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() + + if (i.payments.size() != 1) then throwErr("invalid payment - exact one payment must be attached") else + let pmt = i.payments[0] + let pmtAmount = pmt.amount + + if (assetId != pmt.assetId.value()) then throwErr("invalid asset is in payment - " + assetIdStr + " is expected") else + + let nextUserNumKEY = keyNextUserNum() + let userAddressStr = i.caller.toString() + + let userIsExisting = getString(keyUser2NumMapping(userAddressStr)).isDefined() + let userNumStr = if (userIsExisting) then { + getString(keyUser2NumMapping(userAddressStr)).value() + } else { # new user + this.iof(nextUserNumKEY).toString() + } + let userNum = userNumStr.parseIntValue() + let lockStart = height + + let startBlockKEY = keyLockParamStartBlock(userNumStr) + let durationKEY = keyLockParamDuration(userNumStr) + + let userAmountKEY = keyLockParamUserAmount(userNumStr) + + if ((pmtAmount < minLockAmount) && i.caller != lpStakingPoolsContract) then throwErr("amount is less then minLockAmount=" + minLockAmount.toString()) else + if (duration < minLockDuration) then throwErr("passed duration is less then minLockDuration=" + minLockDuration.toString()) else + if (duration > maxLockDuration) then throwErr("passed duration is greater then maxLockDuration=" + maxLockDuration.toString()) else + if (userIsExisting && (this.iof(startBlockKEY) + this.iof(durationKEY)) >= lockStart) then throwErr("there is an active lock - consider to use increaseLock") else + if (this.ioz(userAmountKEY) > 0) then throwErr("there are locked WXs - consider to use increaseLock " + userAmountKEY) else + + let coeffX8 = fraction(duration, MULT8, maxLockDuration) + let gWxAmountStart = fraction(pmtAmount, coeffX8, MULT8) + + let gWxParamsResultList = invoke(mathContract, "calcGwxParamsREADONLY", [gWxAmountStart, lockStart, duration], []).aal() + # TODO check the following macros: gWxParamsInvokeResult[0].exactAs[Int] + let k = gWxParamsResultList[0].ai() + let b = gWxParamsResultList[1].ai() + let period = gWxParamsResultList[2].ai().toString() + + let totalCachedGwxRaw = getGwxTotal() + + let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNumStr) + let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 + + let arr = if (userIsExisting) then [] else [ + IntegerEntry(nextUserNumKEY, userNum + 1), + StringEntry(keyUser2NumMapping(userAddressStr), userNumStr), + StringEntry(keyNum2UserMapping(userNumStr), userAddressStr) + ] + (arr ++ LockParamsEntry(userAddressStr, userNumStr, pmtAmount, lockStart, duration, k, b, period) + ++ StatsEntry(pmtAmount, duration, 1, if (userIsExisting) then 0 else 1) + :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, duration, k, b, i) + ++ [ + IntegerEntry(userBoostEmissionLastIntegralKEY, boostEmissionIntegral), + IntegerEntry(keyGwxTotal(), totalCachedGwxRaw + gWxAmountStart) + ], gWxAmountStart) +} + +@Callable(i) +func constructor(factoryAddressStr: String, lockAssetIdStr: String, minLockAmount: Int, minDuration: Int, maxDuration: Int, mathContract: String) = { + strict checkCaller = i.mustManager() + + [IntegerEntry(keyNextUserNum(), 0), + StringEntry( + keyConfig(), + formatConfig(lockAssetIdStr, minLockAmount, minDuration, maxDuration, mathContract)), + StringEntry(keyFactoryAddress(), factoryAddressStr) + ] + ++ StatsEntry(0, 0, 0, 0) +} + +@Callable(i) +func lockRef(duration: Int, referrerAddress: String, signature: ByteVector) = { + let (lockActionsResult, gWxAmountStart) = i.lockActions(duration) + let referralAddress = i.caller.toString() + strict refInv = if (referrerAddress == "" || signature == base58'') then unit else { + referralsContractAddressOrFail.invoke("createPair", [referralProgramName, referrerAddress, referralAddress, signature], []) + } + strict updateRefActivity = mathContract.invoke("updateReferralActivity", [i.caller.toString(), gWxAmountStart], []) + + (lockActionsResult, unit) +} + +@Callable(i) +func lock(duration: Int) = { + let (lockActionsResult, gWxAmountStart) = i.lockActions(duration) + strict updateRefActivity = mathContract.invoke("updateReferralActivity", [i.caller.toString(), gWxAmountStart], []) + + (lockActionsResult, unit) +} + +@Callable(i) +func increaseLock(deltaDuration: Int) = { + let cfgArray = readConfigArrayOrFail() + let assetIdStr = cfgArray[IdxCfgAssetId] + let assetId = assetIdStr.fromBase58String() + let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseIntValue() + let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() + + let pmtAmount = extractOptionalPaymentAmountOrFail(i, assetId) + + let userAddress = i.caller + let userAddressStr = userAddress.toString() + let userRecordArray = readLockParamsRecordOrFail(userAddressStr) + + let userNumStr = userRecordArray[IdxLockUserNum] + let userAmount = userRecordArray[IdxLockAmount].parseIntValue() + let lockStart = userRecordArray[IdxLockStart].parseIntValue() + let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() + let lockEnd = lockStart + lockDuration + let remainingDuration = max([lockEnd - height, 0]) + + let userAmountNew = userAmount + pmtAmount + let lockDurationNew = remainingDuration + deltaDuration + + if (deltaDuration < 0) then throwErr("duration is less then zero") else + if (lockDurationNew < minLockDuration) then throwErr("lockDurationNew is less then minLockDuration=" + minLockDuration.toString()) else + if (lockDurationNew > maxLockDuration) then throwErr("deltaDuration + existedLockDuration is greater then maxLockDuration=" + maxLockDuration.toString()) else + #if (lockEnd <= height && userAmount > 0) then throwErr("there is an expired lock - need to unlock before new lock") else + + let coeffX8 = fraction(lockDurationNew, MULT8, maxLockDuration) + let gWxAmountStart = fraction(userAmountNew, coeffX8, MULT8) + + strict updateRefActivity = mathContract.invoke("updateReferralActivity", [i.caller.toString(), gWxAmountStart], []) + + let lockStartNew = height + let gWxParamsResultList = invoke(mathContract, "calcGwxParamsREADONLY", [gWxAmountStart, lockStartNew, lockDurationNew], []).aal() + # TODO check the following macros: gWxParamsInvokeResult[0].exactAs[Int] + let k = gWxParamsResultList[0].ai() + let b = gWxParamsResultList[1].ai() + let period = gWxParamsResultList[2].ai().toString() + + let currUserGwx = getGwxAmount(userAddress) + let gwxDiff = gWxAmountStart - currUserGwx + if (gwxDiff < 0) then throwErr("gwxDiff is less then 0: " + gwxDiff.toString()) else + let totalCachedGwxRaw = getGwxTotal() + + LockParamsEntry(userAddressStr, userNumStr, userAmountNew, lockStartNew, lockDurationNew, k, b, period) + ++ StatsEntry(pmtAmount, deltaDuration, 0, 0) + :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, lockDurationNew, k, b, i) + ++ [ + IntegerEntry(keyGwxTotal(), totalCachedGwxRaw + gwxDiff) + ] +} + +@Callable(i) +func claimWxBoost(lpAssetIdStr: String, userAddressStr: String) = { + if (stakingContract != i.caller) then throwErr("permissions denied") else + let (userBoostAvailable, dataState, debug) = internalClaimWxBoost(lpAssetIdStr, userAddressStr, false) + + (dataState, [userBoostAvailable]) +} + +@Callable(i) +func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { + let (userBoostAvailable, dataState, debug) = internalClaimWxBoost(lpAssetIdStr, userAddressStr, true) + ([], [userBoostAvailable, debug]) +} + +@Callable(i) +func unlock(userAddressStr: String, amount: Int) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let userRecordArray = readLockParamsRecordOrFail(userAddressStr) + + let userNumStr = userRecordArray[IdxLockUserNum] + let userAmount = userRecordArray[IdxLockAmount].parseIntValue() + let lockStart = userRecordArray[IdxLockStart].parseIntValue() + let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() + let lockEnd = lockStart + lockDuration + # TODO: save claimed amount + let userClaimed = 0 + # TODO: gwx amount + let gwxAmount = 0 + + let t = (height - lockStart) / blocksInDay + + let exponent = fraction( + t, + 8 * blocksInDay * MULT8, + lockDuration + ) + let wxWithdrawable = if (height > lockEnd) then { + userAmount - userClaimed + } else { + fraction( + userAmount, + MULT8 - pow(5, 1, exponent, SCALE8, SCALE8, DOWN), + MULT8 + ) + } + + if (amount > wxWithdrawable) + then throwErr("maximum amount to unlock: " + wxWithdrawable.toString()) + else + + let gwxBurned = max([ + amount, + fraction(t, userAmount * MULT8, 4 * daysInYearX8) + ]) + let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") + let lockedGwxAmount = getLockedGwxAmount(userAddress) + + if (gwxRemaining < lockedGwxAmount) + then throwErr("locked gwx amount: " + lockedGwxAmount.toString()) + else + + let cfgArray = readConfigArrayOrFail() + let assetId = cfgArray[IdxCfgAssetId].fromBase58String() + + if (userAmount <= 0) then throwErr("nothing to unlock") else + + let period = mathContract.getInteger(keyNextPeriod()).valueOrElse(0) + + LockParamsEntry(userAddressStr, userNumStr, 0, lockStart, lockDuration, 0, 0, period.toString()) + ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed + :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, 0, 0, i) + :+ ScriptTransfer(userAddress, userAmount, assetId) +} + +@Callable(i) +func gwxUserInfoREADONLY(userAddressStr: String) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let gwxAmount = getGwxAmount(userAddress) + + ([], [gwxAmount]) +} + +# for lp_staking_pools +@Callable(i) +func userMaxDurationREADONLY(userAddressStr: String) = { + let cfgArray = readConfigArrayOrFail() + let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() + + let userRecordOption = this.getString(userAddressStr.keyLockParamsRecord()) + if (userRecordOption == unit) then ([], ("lock", maxLockDuration)) else { + let userRecordArray = userRecordOption.value().split(SEP) + let lockStart = userRecordArray[IdxLockStart].parseIntValue() + let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() + let lockEnd = lockStart + lockDuration + let remainingDuration = max([lockEnd - height, 0]) + + let maxDeltaDuration = maxLockDuration - remainingDuration + + ([], ("increaseLock", maxDeltaDuration)) + } +} + +# TODO: replace by getUserGwxAmountREADONLY (in all contracts) +@Callable(i) +func getUserGwxAmountAtHeightREADONLY(userAddressStr: String) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let gwxAmount = userAddress.getGwxAmount() + + ([], gwxAmount) +} + +@Callable(i) +func getGwxTotalREADONLY() = { + ([], getGwxTotal()) +} + +# call this function when wxEmissionRate or boostCoeff changes +@Callable(i) +func onBoostEmissionUpdate() = { + strict checkCaller = i.caller == emissionContract || i.mustManager() + refreshBoostEmissionIntegral() +} + +# Staked vote = 0 if staked amount = 0 +# Staked vote = last finalized vote if staked amount ≥ 0 +# So, stakedVote = [stakedAmount ≥ 0] * lastFinalizedVote +# └ this is the Iverson bracket, not the array's one +# Staked vote updates when staked amount goes from zero to non-zero or vice versa +# Staked vote integral and voting result integral should be updated when it happens +# ╭­ edge = true (rising) +# staked amount > 0: ┌───┐ +# staked amount = 0: ───┘ └─── +# ╰ edge = false (falling) +@Callable(i) +func onStakedVoteUpdate(lpAssetIdStr: String, userAddressStr: String, edge: Boolean) = { + strict checkCaller = i.caller == stakingContract || i.mustManager() + let actions = lpAssetIdStr.refreshVoteStakedIntegral(userAddressStr, edge) + + (actions, unit) +} + +@Callable(i) +func getVotingResultStakedREADONLY(lpAssetIdStr: String) = { + ([], lpAssetIdStr.getVotingResultStaked()) +} + +@Callable(i) +func getVotingResultStakedIntegralREADONLY(lpAssetIdStr: String) = { + ([], lpAssetIdStr.getVotingResultStakedIntegral()) +} + +@Callable(i) +func getUserVoteFinalizedREADONLY(lpAssetIdStr: String, userAddressStr: String) = { + ([], lpAssetIdStr.getUserVoteFinalized(userAddressStr)) +} + +@Callable(i) +func getUserVoteStakedIntegralREADONLY(lpAssetIdStr: String, userAddressStr: String) = { + ([], lpAssetIdStr.getUserVoteStakedIntegral(userAddressStr)) +} + +@Verifier(tx) +func verify() = { + let targetPublicKey = match managerPublicKeyOrUnit() { + case pk: ByteVector => pk + case _: Unit => tx.senderPublicKey + } + sigVerify(tx.bodyBytes, tx.proofs[0], targetPublicKey) +} From af1af0f9decca0b2d7be9e584928280637fc5973 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 14 Jul 2023 18:18:26 +0500 Subject: [PATCH 05/86] refactor --- ride/boosting_v2.ride | 327 ++++++++++++++++++++---------------------- 1 file changed, 153 insertions(+), 174 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index fe950537c..620ab9a95 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -10,8 +10,8 @@ # * "%s__lpStakingPoolsContract": String let SEP = "__" -let SCALE8 = 8 -let MULT8 = 100000000 +let SCALE8 = 8 +let MULT8 = 100000000 let POOLWEIGHTMULT = MULT8 let contractFilename = "boosting.ride" @@ -24,38 +24,19 @@ let blockInMonthX8 = blocksInYearX8 / monthsInYear func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") func throwErr(msg: String) = msg.wrapErr().throw() -# getStringOrFail -func strf(address: Address, key: String) = address.getString(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) -# getIntOrZero -func ioz(address: Address, key: String) = address.getInteger(key).valueOrElse(0) -# getIntOrDefault -func iod(address: Address, key: String, defaultVal: Int) = address.getInteger(key).valueOrElse(defaultVal) -# getIntOrFail -func iof(address: Address, key: String) = address.getInteger(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) -func abs(val: Int) = if (val < 0) then -val else val - -# asAnyList -func aal(val: Any) = { - match val { - case valAnyLyst: List[Any] => valAnyLyst - case _ => throwErr("fail to cast into List[Any]") - } -} +func getStringOrFail(address: Address, key: String) = address.getString(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) +func getIntOrZero(address: Address, key: String) = address.getInteger(key).valueOrElse(0) +func getIntOrDefault(address: Address, key: String, defaultVal: Int) = address.getInteger(key).valueOrElse(defaultVal) +func getIntOrFail(address: Address, key: String) = address.getInteger(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) -# asInt -func ai(val: Any) = { - match val { - case valInt: Int => valInt - case _ => throwErr("fail to cast into Int") - } -} +func abs(val: Int) = if (val < 0) then -val else val func ensurePositive(v: Int, m: String|Unit) = { if (v >= 0) then v else throwErr(m.valueOrElse("value") + " should be positive") } -func keyReferralsContractAddress() = ["%s%s", "config", "referralsContractAddress"].makeString(SEP) -let referralsContractAddressOrFail = this.strf(keyReferralsContractAddress()).addressFromStringValue() +func keyReferralsContractAddress() = ["%s%s", "config", "referralsContractAddress"].makeString(SEP) +let referralsContractAddressOrFail = this.getStringOrFail(keyReferralsContractAddress()).addressFromStringValue() let keyReferralProgramName = ["%s%s", "referral", "programName"].makeString(SEP) let referralProgramNameDefault = "wxlock" @@ -63,33 +44,31 @@ let referralProgramName = this.getString(keyReferralProgramName).valueOrElse(ref # FACTORY API # own factory address key -func keyFactoryAddress() = "%s%s__config__factoryAddress" - -let IdxFactoryCfgStakingDapp = 1 -let IdxFactoryCfgBoostingDapp = 2 -let IdxFactoryCfgIdoDapp = 3 -let IdxFactoryCfgTeamDapp = 4 -let IdxFactoryCfgEmissionDapp = 5 -let IdxFactoryCfgRestDapp = 6 -let IdxFactoryCfgSlippageDapp = 7 -let IdxFactoryCfgDaoDapp = 8 -let IdxFactoryCfgMarketingDapp = 9 -let IdxFactoryCfgGwxRewardDapp = 10 -let IdxFactoryCfgBirdsDapp = 11 - -func keyFactoryCfg() = "%s__factoryConfig" -func keyFactoryLpList() = "%s__lpTokensList" # not used anymore +func keyFactoryAddress() = "%s%s__config__factoryAddress" + +let IdxFactoryCfgStakingDapp = 1 +let IdxFactoryCfgBoostingDapp = 2 +let IdxFactoryCfgIdoDapp = 3 +let IdxFactoryCfgTeamDapp = 4 +let IdxFactoryCfgEmissionDapp = 5 +let IdxFactoryCfgRestDapp = 6 +let IdxFactoryCfgSlippageDapp = 7 +let IdxFactoryCfgDaoDapp = 8 +let IdxFactoryCfgMarketingDapp = 9 +let IdxFactoryCfgGwxRewardDapp = 10 +let IdxFactoryCfgBirdsDapp = 11 + +func keyFactoryCfg() = "%s__factoryConfig" func keyFactoryLpAssetToPoolContractAddress(lpAssetStr: String) = makeString(["%s%s%s", lpAssetStr, "mappings__lpAsset2PoolContract"], SEP) func keyFactoryPoolWeight(contractAddress: String) = { ["%s%s", "poolWeight", contractAddress].makeString(SEP) } func keyFactoryPoolWeightHistory(poolAddress: String, num: Int) = {"%s%s__poolWeight__" + poolAddress + "__" + num.toString()} -func readFactoryAddressOrFail() = this.strf(keyFactoryAddress()).addressFromStringValue() -func readLpList() = readFactoryAddressOrFail().getString(keyFactoryLpList()).valueOrElse("").split(SEP) -func readFactoryCfgOrFail(factory: Address) = factory.strf(keyFactoryCfg()).split(SEP) +func readFactoryAddressOrFail() = this.getStringOrFail(keyFactoryAddress()).addressFromStringValue() +func readFactoryCfgOrFail(factory: Address) = factory.getStringOrFail(keyFactoryCfg()).split(SEP) func getBoostingAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgBoostingDapp].addressFromStringValue() func getEmissionAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgEmissionDapp].addressFromStringValue() -func getStakingAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgStakingDapp].addressFromStringValue() -func getGwxRewardAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgGwxRewardDapp].addressFromStringValue() +func getStakingAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgStakingDapp].addressFromStringValue() +func getGwxRewardAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgGwxRewardDapp].addressFromStringValue() func keyManagerPublicKey() = "%s__managerPublicKey" func keyManagerVaultAddress() = "%s__managerVaultAddress" @@ -104,39 +83,39 @@ func keyEmissionDurationInBlocks() = "%s%s__emission__duration" func keyEmissionEndBlock() = "%s%s__emission__endBlock" # GWX REWARD (MATH) API -func keyNextPeriod() = "%s__nextPeriod" +func keyNextPergetIntOrDefault() = "%s__nextPeriod" func keyGwxRewardEmissionStartHeight() = "%s%s__gwxRewardEmissionPart__startHeight" # OWN KEYS -let IdxCfgAssetId = 1 -let IdxCfgMinLockAmount = 2 -let IdxCfgMinLockDuration = 3 -let IdxCfgMaxLockDuration = 4 -let IdxCfgMathContract = 5 +let IdxCfgAssetId = 1 +let IdxCfgMinLockAmount = 2 +let IdxCfgMinLockDuration = 3 +let IdxCfgMaxLockDuration = 4 +let IdxCfgMathContract = 5 func keyConfig() = {"%s__config"} -func readConfigArrayOrFail() = this.strf(keyConfig()).split(SEP) +func readConfigArrayOrFail() = this.getStringOrFail(keyConfig()).split(SEP) let mathContract = readConfigArrayOrFail()[IdxCfgMathContract].addressFromStringValue() func formatConfigS(assetId: String, minLockAmount: String, minLockDuration: String, maxLockDuration: String, mathContract: String) = { makeString([ "%s%d%d%d", - assetId, # 1 - minLockAmount, # 2 - minLockDuration, # 3 - maxLockDuration, # 4 - mathContract # 5 + assetId, # 1 + minLockAmount, # 2 + minLockDuration, # 3 + maxLockDuration, # 4 + mathContract # 5 ], SEP) } func formatConfig(assetId: String, minLockAmount: Int, minLockDuration: Int, maxLockDuration: Int, mathContract: String) = { formatConfigS( - assetId, # 1 - minLockAmount.toString(), # 2 - minLockDuration.toString(), # 3 - maxLockDuration.toString(), # 4 - mathContract # 5 + assetId, # 1 + minLockAmount.toString(), # 2 + minLockDuration.toString(), # 3 + maxLockDuration.toString(), # 4 + mathContract # 5 ) } @@ -164,71 +143,71 @@ func mustManager(i: Invocation) = { } } -let IdxLockUserNum = 1 -let IdxLockAmount = 2 -let IdxLockStart = 3 -let IdxLockDuration = 4 -let IdxLockParamK = 5 -let IdxLockParamB = 6 +let IdxLockUserNum = 1 +let IdxLockAmount = 2 +let IdxLockStart = 3 +let IdxLockDuration = 4 +let IdxLockParamK = 5 +let IdxLockParamB = 6 func keyLockParamsRecord(userAddress: String) = makeString(["%s%s__lock", userAddress], SEP) -func readLockParamsRecordOrFail(userAddress: String) = this.strf(keyLockParamsRecord(userAddress)).split(SEP) +func readLockParamsRecordOrFail(userAddress: String) = this.getStringOrFail(keyLockParamsRecord(userAddress)).split(SEP) func formatLockParamsRecordS(userNum: String, amount: String, start: String, duration: String, paramK: String, paramB: String, lastUpdTimestamp: String, gwxAmount: String) = { makeString([ - "%d%d%d%d%d%d%d%d", # 0 - userNum, # 1 - amount, # 2 - start, # 3 - duration, # 4 - paramK, # 5 - paramB, # 6 + "%d%d%d%d%d%d%d%d", # 0 + userNum, # 1 + amount, # 2 + start, # 3 + duration, # 4 + paramK, # 5 + paramB, # 6 lastUpdTimestamp, #7 - gwxAmount # 8 + gwxAmount # 8 ], SEP) } func formatLockParamsRecord(userNum: String, amount: Int, start: Int, duration: Int, paramK: Int, paramB: Int, gwxAmount: Int) = { formatLockParamsRecordS( - userNum, # 1 - amount.toString(), # 2 - start.toString(), # 3 - duration.toString(), # 4 - paramK.toString(), # 5 - paramB.toString(), # 6 + userNum, # 1 + amount.toString(), # 2 + start.toString(), # 3 + duration.toString(), # 4 + paramK.toString(), # 5 + paramB.toString(), # 6 lastBlock.timestamp.toString(), # 7 - gwxAmount.toString() # 8 + gwxAmount.toString() # 8 ) } # mappings -func keyNextUserNum() = "%s__nextUserNum" -func keyUser2NumMapping(userAddress: String) = makeString(["%s%s%s__mapping__user2num", userAddress], SEP) -func keyNum2UserMapping(num: String) = makeString(["%s%s%s__mapping__num2user", num], SEP) - -func keyLockParamUserAmount(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "amount"], SEP) -func keyLockParamStartBlock(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "start"], SEP) -func keyLockParamDuration(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "duration"], SEP) -func keyLockParamK(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "k"], SEP) -func keyLockParamB(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "b"], SEP) -func keyLockParamByPeriodK(userNum: String, period: String) = makeString(["%s%d%s%d__paramByPeriod", userNum, "k", period], SEP) -func keyLockParamByPeriodB(userNum: String, period: String) = makeString(["%s%d%s%d__paramByPeriod", userNum, "b", period], SEP) +func keyNextUserNum() = "%s__nextUserNum" +func keyUser2NumMapping(userAddress: String) = makeString(["%s%s%s__mapping__user2num", userAddress], SEP) +func keyNum2UserMapping(num: String) = makeString(["%s%s%s__mapping__num2user", num], SEP) + +func keyLockParamUserAmount(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "amount"], SEP) +func keyLockParamStartBlock(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "start"], SEP) +func keyLockParamDuration(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "duration"], SEP) +func keyLockParamK(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "k"], SEP) +func keyLockParamB(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "b"], SEP) +func keyLockParamByPeriodK(userNum: String, period: String) = makeString(["%s%d%s%d__paramByPeriod", userNum, "k", period], SEP) +func keyLockParamByPeriodB(userNum: String, period: String) = makeString(["%s%d%s%d__paramByPeriod", userNum, "b", period], SEP) # TODO - consider to concat -func keyLockParamTotalAmount() = "%s%s__stats__activeTotalLocked" -func keyStatsLocksDurationSumInBlocks() = "%s%s__stats__locksDurationSumInBlocks" -func keyStatsLocksCount() = "%s%s__stats__locksCount" -func keyStatsUsersCount() = "%s%s__stats__activeUsersCount" +func keyLockParamTotalAmount() = "%s%s__stats__activeTotalLocked" +func keyStatsLocksDurationSumInBlocks() = "%s%s__stats__locksDurationSumInBlocks" +func keyStatsLocksCount() = "%s%s__stats__locksCount" +func keyStatsUsersCount() = "%s%s__stats__activeUsersCount" # boost integral -func keyUserBoostEmissionLastINTEGRAL(userNum: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum], SEP) +func keyUserBoostEmissionLastINTEGRAL(userNum: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum], SEP) func keyUserLpBoostEmissionLastINTEGRAL(userNum: String, lpAssetId: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum, lpAssetId], SEP) -func keyUserMaxBoostINTEGRAL(userNum: String) = makeString(["%s%d__maxBoostInt", userNum], SEP) -func keyTotalMaxBoostINTEGRAL() = "%s%s__maxBoostInt__total" -func keyUserBoostAvalaibleToClaimTotal(userNum: String) = makeString(["%s%d__userBoostAvaliableToClaimTotal", userNum], SEP) -func keyUserBoostClaimed(userNum: String) = makeString(["%s%d__userBoostClaimed", userNum], SEP) +func keyUserMaxBoostINTEGRAL(userNum: String) = makeString(["%s%d__maxBoostInt", userNum], SEP) +func keyTotalMaxBoostINTEGRAL() = "%s%s__maxBoostInt__total" +func keyUserBoostAvalaibleToClaimTotal(userNum: String) = makeString(["%s%d__userBoostAvaliableToClaimTotal", userNum], SEP) +func keyUserBoostClaimed(userNum: String) = makeString(["%s%d__userBoostClaimed", userNum], SEP) func keyGwxTotal() = "%s%s__gwx__total" @@ -275,10 +254,10 @@ func keyStakedByUser(userAddressStr: String, lpAssetIdStr: String) = ["%s%s%s", # GLOBAL VARIABLES # CONSTRUCTOR IS NOT FAILED BECAUSE GLOBAL VARIABLES ARE NOT USED -let factoryContract = readFactoryAddressOrFail() -let factoryCfg = factoryContract.readFactoryCfgOrFail() -let emissionContract = factoryCfg.getEmissionAddressOrFail() -let stakingContract = factoryCfg.getStakingAddressOrFail() +let factoryContract = readFactoryAddressOrFail() +let factoryCfg = factoryContract.readFactoryCfgOrFail() +let emissionContract = factoryCfg.getEmissionAddressOrFail() +let stakingContract = factoryCfg.getStakingAddressOrFail() let gwxRewardContract = factoryCfg.getGwxRewardAddressOrFail() let lpStakingPoolsContract = ["%s", "lpStakingPoolsContract"].makeString(SEP) .getString().valueOrErrorMessage("lp_staking_pools contract address is undefined".wrapErr()) @@ -329,12 +308,12 @@ func StatsEntry(totalLockedInc: Int, durationInc: Int, lockCountInc: Int, usersC let locksDurationSumInBlocksKEY = keyStatsLocksDurationSumInBlocks() let locksCountKEY = keyStatsLocksCount() let usersCountKEY = keyStatsUsersCount() - let totalAmountKEY = keyLockParamTotalAmount() + let totalAmountKEY = keyLockParamTotalAmount() - let locksDurationSumInBlocks = this.ioz(locksDurationSumInBlocksKEY) - let locksCount = this.ioz(locksCountKEY) - let usersCount = this.ioz(usersCountKEY) - let totalAmount = this.ioz(totalAmountKEY) + let locksDurationSumInBlocks = this.getIntOrZero(locksDurationSumInBlocksKEY) + let locksCount = this.getIntOrZero(locksCountKEY) + let usersCount = this.getIntOrZero(usersCountKEY) + let totalAmount = this.getIntOrZero(totalAmountKEY) [IntegerEntry(locksDurationSumInBlocksKEY, locksDurationSumInBlocks + durationInc), IntegerEntry(locksCountKEY, locksCount + lockCountInc), @@ -352,13 +331,13 @@ func calcGwxAmount(amount: Int, duration: Int) = { func LockParamsEntry(userAddress: String, userNum: String, amount: Int, start: Int, duration: Int, k: Int, b: Int, period: String) = { - let userAmountKEY = keyLockParamUserAmount(userNum) - let startBlockKEY = keyLockParamStartBlock(userNum) - let durationKEY = keyLockParamDuration(userNum) - let kKEY = keyLockParamK(userNum) - let bKEY = keyLockParamB(userNum) - let kByPeriodKEY = keyLockParamByPeriodK(userNum, period) - let bByPeriodKEY = keyLockParamByPeriodB(userNum, period) + let userAmountKEY = keyLockParamUserAmount(userNum) + let startBlockKEY = keyLockParamStartBlock(userNum) + let durationKEY = keyLockParamDuration(userNum) + let kKEY = keyLockParamK(userNum) + let bKEY = keyLockParamB(userNum) + let kByPeriodKEY = keyLockParamByPeriodK(userNum, period) + let bByPeriodKEY = keyLockParamByPeriodB(userNum, period) let gwxAmount = calcGwxAmount(amount, duration) [IntegerEntry(userAmountKEY, amount), @@ -374,7 +353,7 @@ func LockParamsEntry(userAddress: String, userNum: String, amount: Int, start: I } func extractOptionalPaymentAmountOrFail(i: Invocation, expectedAssetId: ByteVector) = { - if (i.payments.size() > 1) then throwErr("only one payment is allowed") else + if (i.payments.size() > 1) then throwErr("only one payment is allowed") else if (i.payments.size() == 0) then 0 else let pmt = i.payments[0] if (pmt.assetId.value() != expectedAssetId) then throwErr("invalid asset id in payment") else @@ -533,10 +512,10 @@ func getStakedVotesIntegralsDiff(lpAssetIdStr: String, userAddressStr: String) = # Actions should be applied if wxEmissionPerBlock or boostCoeff changes func refreshBoostEmissionIntegral() = { - let wxEmissionPerBlock = emissionContract.iof(keyEmissionRatePerBlockCurrent()) + let wxEmissionPerBlock = emissionContract.getIntOrFail(keyEmissionRatePerBlockCurrent()) let boostingV2LastUpdateHeightOption = this.getInteger(keyBoostingV2LastUpdateHeight()) let boostingV2IngergalOption = this.getInteger(keyBoostingV2Integral()) - let emissionEnd = emissionContract.iof(keyEmissionEndBlock()) + let emissionEnd = emissionContract.getIntOrFail(keyEmissionEndBlock()) let h = if (height > emissionEnd) then emissionEnd else height let dh = match boostingV2LastUpdateHeightOption { @@ -579,7 +558,7 @@ func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNumStr) let userBoostEmissionLastIntegral = this.getInteger(userLpBoostEmissionLastIntegralKEY) - .valueOrElse(this.ioz(userBoostEmissionLastIntegralKEY)) + .valueOrElse(this.getIntOrZero(userBoostEmissionLastIntegralKEY)) let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 let userBoostEmissionIntegral = boostEmissionIntegral - userBoostEmissionLastIntegral @@ -611,12 +590,12 @@ func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly } func lockActions(i: Invocation, duration: Int) = { - let cfgArray = readConfigArrayOrFail() - let assetIdStr = cfgArray[IdxCfgAssetId] - let assetId = assetIdStr.fromBase58String() - let minLockAmount = cfgArray[IdxCfgMinLockAmount].parseIntValue() - let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseIntValue() - let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() + let cfgArray = readConfigArrayOrFail() + let assetIdStr = cfgArray[IdxCfgAssetId] + let assetId = assetIdStr.fromBase58String() + let minLockAmount = cfgArray[IdxCfgMinLockAmount].parseIntValue() + let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseIntValue() + let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() if (i.payments.size() != 1) then throwErr("invalid payment - exact one payment must be attached") else let pmt = i.payments[0] @@ -624,37 +603,37 @@ func lockActions(i: Invocation, duration: Int) = { if (assetId != pmt.assetId.value()) then throwErr("invalid asset is in payment - " + assetIdStr + " is expected") else - let nextUserNumKEY = keyNextUserNum() - let userAddressStr = i.caller.toString() + let nextUserNumKEY = keyNextUserNum() + let userAddressStr = i.caller.toString() let userIsExisting = getString(keyUser2NumMapping(userAddressStr)).isDefined() let userNumStr = if (userIsExisting) then { getString(keyUser2NumMapping(userAddressStr)).value() } else { # new user - this.iof(nextUserNumKEY).toString() + this.getIntOrFail(nextUserNumKEY).toString() } let userNum = userNumStr.parseIntValue() let lockStart = height - let startBlockKEY = keyLockParamStartBlock(userNumStr) - let durationKEY = keyLockParamDuration(userNumStr) + let startBlockKEY = keyLockParamStartBlock(userNumStr) + let durationKEY = keyLockParamDuration(userNumStr) - let userAmountKEY = keyLockParamUserAmount(userNumStr) + let userAmountKEY = keyLockParamUserAmount(userNumStr) if ((pmtAmount < minLockAmount) && i.caller != lpStakingPoolsContract) then throwErr("amount is less then minLockAmount=" + minLockAmount.toString()) else if (duration < minLockDuration) then throwErr("passed duration is less then minLockDuration=" + minLockDuration.toString()) else if (duration > maxLockDuration) then throwErr("passed duration is greater then maxLockDuration=" + maxLockDuration.toString()) else - if (userIsExisting && (this.iof(startBlockKEY) + this.iof(durationKEY)) >= lockStart) then throwErr("there is an active lock - consider to use increaseLock") else - if (this.ioz(userAmountKEY) > 0) then throwErr("there are locked WXs - consider to use increaseLock " + userAmountKEY) else + if (userIsExisting && (this.getIntOrFail(startBlockKEY) + this.getIntOrFail(durationKEY)) >= lockStart) then throwErr("there is an active lock - consider to use increaseLock") else + if (this.getIntOrZero(userAmountKEY) > 0) then throwErr("there are locked WXs - consider to use increaseLock " + userAmountKEY) else let coeffX8 = fraction(duration, MULT8, maxLockDuration) let gWxAmountStart = fraction(pmtAmount, coeffX8, MULT8) - let gWxParamsResultList = invoke(mathContract, "calcGwxParamsREADONLY", [gWxAmountStart, lockStart, duration], []).aal() + let gWxParamsResultList = invoke(mathContract, "calcGwxParamsREADONLY", [gWxAmountStart, lockStart, duration], []).exactAs[List[Any]] # TODO check the following macros: gWxParamsInvokeResult[0].exactAs[Int] - let k = gWxParamsResultList[0].ai() - let b = gWxParamsResultList[1].ai() - let period = gWxParamsResultList[2].ai().toString() + let k = gWxParamsResultList[0].exactAs[Int] + let b = gWxParamsResultList[1].exactAs[Int] + let period = gWxParamsResultList[2].exactAs[Int].toString() let totalCachedGwxRaw = getGwxTotal() @@ -710,27 +689,27 @@ func lock(duration: Int) = { @Callable(i) func increaseLock(deltaDuration: Int) = { - let cfgArray = readConfigArrayOrFail() - let assetIdStr = cfgArray[IdxCfgAssetId] - let assetId = assetIdStr.fromBase58String() - let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseIntValue() - let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() + let cfgArray = readConfigArrayOrFail() + let assetIdStr = cfgArray[IdxCfgAssetId] + let assetId = assetIdStr.fromBase58String() + let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseIntValue() + let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() let pmtAmount = extractOptionalPaymentAmountOrFail(i, assetId) let userAddress = i.caller - let userAddressStr = userAddress.toString() - let userRecordArray = readLockParamsRecordOrFail(userAddressStr) + let userAddressStr = userAddress.toString() + let userRecordArray = readLockParamsRecordOrFail(userAddressStr) - let userNumStr = userRecordArray[IdxLockUserNum] - let userAmount = userRecordArray[IdxLockAmount].parseIntValue() - let lockStart = userRecordArray[IdxLockStart].parseIntValue() - let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() - let lockEnd = lockStart + lockDuration - let remainingDuration = max([lockEnd - height, 0]) + let userNumStr = userRecordArray[IdxLockUserNum] + let userAmount = userRecordArray[IdxLockAmount].parseIntValue() + let lockStart = userRecordArray[IdxLockStart].parseIntValue() + let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() + let lockEnd = lockStart + lockDuration + let remainingDuration = max([lockEnd - height, 0]) - let userAmountNew = userAmount + pmtAmount - let lockDurationNew = remainingDuration + deltaDuration + let userAmountNew = userAmount + pmtAmount + let lockDurationNew = remainingDuration + deltaDuration if (deltaDuration < 0) then throwErr("duration is less then zero") else if (lockDurationNew < minLockDuration) then throwErr("lockDurationNew is less then minLockDuration=" + minLockDuration.toString()) else @@ -743,11 +722,11 @@ func increaseLock(deltaDuration: Int) = { strict updateRefActivity = mathContract.invoke("updateReferralActivity", [i.caller.toString(), gWxAmountStart], []) let lockStartNew = height - let gWxParamsResultList = invoke(mathContract, "calcGwxParamsREADONLY", [gWxAmountStart, lockStartNew, lockDurationNew], []).aal() + let gWxParamsResultList = invoke(mathContract, "calcGwxParamsREADONLY", [gWxAmountStart, lockStartNew, lockDurationNew], []).exactAs[List[Any]] # TODO check the following macros: gWxParamsInvokeResult[0].exactAs[Int] - let k = gWxParamsResultList[0].ai() - let b = gWxParamsResultList[1].ai() - let period = gWxParamsResultList[2].ai().toString() + let k = gWxParamsResultList[0].exactAs[Int] + let b = gWxParamsResultList[1].exactAs[Int] + let period = gWxParamsResultList[2].exactAs[Int].toString() let currUserGwx = getGwxAmount(userAddress) let gwxDiff = gWxAmountStart - currUserGwx @@ -780,13 +759,13 @@ func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { func unlock(userAddressStr: String, amount: Int) = { let userAddress = userAddressStr.addressFromString() .valueOrErrorMessage(wrapErr("invalid user address")) - let userRecordArray = readLockParamsRecordOrFail(userAddressStr) + let userRecordArray = readLockParamsRecordOrFail(userAddressStr) - let userNumStr = userRecordArray[IdxLockUserNum] - let userAmount = userRecordArray[IdxLockAmount].parseIntValue() - let lockStart = userRecordArray[IdxLockStart].parseIntValue() - let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() - let lockEnd = lockStart + lockDuration + let userNumStr = userRecordArray[IdxLockUserNum] + let userAmount = userRecordArray[IdxLockAmount].parseIntValue() + let lockStart = userRecordArray[IdxLockStart].parseIntValue() + let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() + let lockEnd = lockStart + lockDuration # TODO: save claimed amount let userClaimed = 0 # TODO: gwx amount @@ -824,12 +803,12 @@ func unlock(userAddressStr: String, amount: Int) = { then throwErr("locked gwx amount: " + lockedGwxAmount.toString()) else - let cfgArray = readConfigArrayOrFail() - let assetId = cfgArray[IdxCfgAssetId].fromBase58String() + let cfgArray = readConfigArrayOrFail() + let assetId = cfgArray[IdxCfgAssetId].fromBase58String() if (userAmount <= 0) then throwErr("nothing to unlock") else - let period = mathContract.getInteger(keyNextPeriod()).valueOrElse(0) + let period = mathContract.getInteger(keyNextPergetIntOrDefault()).valueOrElse(0) LockParamsEntry(userAddressStr, userNumStr, 0, lockStart, lockDuration, 0, 0, period.toString()) ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed From f9223f5064ef986190206d027ad7cbefd4ab69ef Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 17 Jul 2023 20:11:08 +0500 Subject: [PATCH 06/86] wip --- ride/boosting_v2.ride | 232 +++++++++++++----------------------------- 1 file changed, 71 insertions(+), 161 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 620ab9a95..2a2a926fb 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -20,6 +20,7 @@ let daysInYearX8 = fraction(3652425, MULT8, 10000) let blocksInYearX8 = daysInYearX8 * blocksInDay let monthsInYear = 12 let blockInMonthX8 = blocksInYearX8 / monthsInYear +let maxLockDurationX8 = 4 * blocksInYearX8 func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") func throwErr(msg: String) = msg.wrapErr().throw() @@ -143,43 +144,34 @@ func mustManager(i: Invocation) = { } } -let IdxLockUserNum = 1 -let IdxLockAmount = 2 -let IdxLockStart = 3 -let IdxLockDuration = 4 -let IdxLockParamK = 5 -let IdxLockParamB = 6 +let IdxLockAmount = 1 +let IdxLockStart = 2 +let IdxLockDuration = 3 +let IdxLockParamLastUpdateTimestamp = 4 +let IdxLockParamGwxAmount = 5 +let IdxLockParamWxClaimed = 6 -func keyLockParamsRecord(userAddress: String) = makeString(["%s%s__lock", userAddress], SEP) -func readLockParamsRecordOrFail(userAddress: String) = this.getStringOrFail(keyLockParamsRecord(userAddress)).split(SEP) +func keyLockParamsRecord(userAddress: Address, txId: ByteVector) = makeString(["%s%s%s__lock", userAddress.toString(), txId.toBase58String()], SEP) +func readLockParamsRecordOrFail(userAddress: Address, txId: ByteVector) = this.getStringOrFail(keyLockParamsRecord(userAddress, txId)).split(SEP) -func formatLockParamsRecordS(userNum: String, amount: String, start: String, duration: String, paramK: String, paramB: String, lastUpdTimestamp: String, gwxAmount: String) = { +func keyUserGwxAmountTotal(userAddress: Address) = makeString(["%s%s%s__gwxAmountTotal", userAddress.toString()], SEP) +func formatLockParamsRecord( + amount: Int, + start: Int, + duration: Int, + gwxAmount: Int, + wxClaimed: Int +) = { makeString([ - "%d%d%d%d%d%d%d%d", # 0 - userNum, # 1 - amount, # 2 - start, # 3 - duration, # 4 - paramK, # 5 - paramB, # 6 - lastUpdTimestamp, #7 - gwxAmount # 8 - ], - SEP) -} - -func formatLockParamsRecord(userNum: String, amount: Int, start: Int, duration: Int, paramK: Int, paramB: Int, gwxAmount: Int) = { - formatLockParamsRecordS( - userNum, # 1 - amount.toString(), # 2 - start.toString(), # 3 - duration.toString(), # 4 - paramK.toString(), # 5 - paramB.toString(), # 6 - lastBlock.timestamp.toString(), # 7 - gwxAmount.toString() # 8 - ) + "%d%d%d%d%d%d%d", # 0 + amount.toString(), # 1 + start.toString(), # 2 + duration.toString(), # 3 + lastBlock.timestamp.toString(), # 4 + gwxAmount.toString(), # 5 + wxClaimed.toString() # 6 + ], SEP) } # mappings @@ -187,14 +179,6 @@ func keyNextUserNum() = "%s__nextUserNum" func keyUser2NumMapping(userAddress: String) = makeString(["%s%s%s__mapping__user2num", userAddress], SEP) func keyNum2UserMapping(num: String) = makeString(["%s%s%s__mapping__num2user", num], SEP) -func keyLockParamUserAmount(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "amount"], SEP) -func keyLockParamStartBlock(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "start"], SEP) -func keyLockParamDuration(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "duration"], SEP) -func keyLockParamK(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "k"], SEP) -func keyLockParamB(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "b"], SEP) -func keyLockParamByPeriodK(userNum: String, period: String) = makeString(["%s%d%s%d__paramByPeriod", userNum, "k", period], SEP) -func keyLockParamByPeriodB(userNum: String, period: String) = makeString(["%s%d%s%d__paramByPeriod", userNum, "b", period], SEP) - # TODO - consider to concat func keyLockParamTotalAmount() = "%s%s__stats__activeTotalLocked" func keyStatsLocksDurationSumInBlocks() = "%s%s__stats__locksDurationSumInBlocks" @@ -202,12 +186,12 @@ func keyStatsLocksCount() = "%s%s__stats__locksCount" func keyStatsUsersCount() = "%s%s__stats__activeUsersCount" # boost integral -func keyUserBoostEmissionLastINTEGRAL(userNum: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum], SEP) -func keyUserLpBoostEmissionLastINTEGRAL(userNum: String, lpAssetId: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum, lpAssetId], SEP) -func keyUserMaxBoostINTEGRAL(userNum: String) = makeString(["%s%d__maxBoostInt", userNum], SEP) +func keyUserBoostEmissionLastINTEGRAL(userNum: Int) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum.toString()], SEP) +func keyUserLpBoostEmissionLastINTEGRAL(userNum: Int, lpAssetId: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum.toString(), lpAssetId], SEP) +func keyUserMaxBoostINTEGRAL(userNum: Int) = makeString(["%s%d__maxBoostInt", userNum.toString()], SEP) func keyTotalMaxBoostINTEGRAL() = "%s%s__maxBoostInt__total" -func keyUserBoostAvalaibleToClaimTotal(userNum: String) = makeString(["%s%d__userBoostAvaliableToClaimTotal", userNum], SEP) -func keyUserBoostClaimed(userNum: String) = makeString(["%s%d__userBoostClaimed", userNum], SEP) +func keyUserBoostAvalaibleToClaimTotal(userNum: Int) = makeString(["%s%d__userBoostAvaliableToClaimTotal", userNum.toString()], SEP) +func keyUserBoostClaimed(userNum: Int) = makeString(["%s%d__userBoostClaimed", userNum.toString()], SEP) func keyGwxTotal() = "%s%s__gwx__total" @@ -268,6 +252,13 @@ let votingEmissionContract = factoryContract.getStringValue(keyVotingEmissionCon let boostCoeff = emissionContract.invoke("getBoostCoeffREADONLY", [], []).exactAs[Int] +func userNumberByAddressOrFail(userAddress: Address) = { + match this.getString(keyUser2NumMapping(userAddress.toString())) { + case s: String => s.parseInt().valueOrErrorMessage(wrapErr("invalid user number")) + case _: Unit => throwErr("invalid user") + } +} + func getGwxTotal() = { this.getInteger(keyGwxTotal()).valueOrElse(0) } @@ -321,35 +312,29 @@ func StatsEntry(totalLockedInc: Int, durationInc: Int, lockCountInc: Int, usersC IntegerEntry(totalAmountKEY, totalAmount + totalLockedInc)] } -func calcGwxAmount(amount: Int, duration: Int) = { +func calcGwxAmountStart(amount: Int, duration: Int) = { fraction( amount, duration * MULT8, - 4 * blocksInYearX8 + maxLockDurationX8 ) } -func LockParamsEntry(userAddress: String, userNum: String, amount: Int, start: Int, duration: Int, k: Int, b: Int, period: String) = { - - let userAmountKEY = keyLockParamUserAmount(userNum) - let startBlockKEY = keyLockParamStartBlock(userNum) - let durationKEY = keyLockParamDuration(userNum) - let kKEY = keyLockParamK(userNum) - let bKEY = keyLockParamB(userNum) - let kByPeriodKEY = keyLockParamByPeriodK(userNum, period) - let bByPeriodKEY = keyLockParamByPeriodB(userNum, period) - - let gwxAmount = calcGwxAmount(amount, duration) - [IntegerEntry(userAmountKEY, amount), - IntegerEntry(startBlockKEY, start), - IntegerEntry(durationKEY, duration), - IntegerEntry(kKEY, k), - IntegerEntry(bKEY, b), - IntegerEntry(kByPeriodKEY, k), - IntegerEntry(bByPeriodKEY, b), - StringEntry( # fixme: it does not work for multi lock. The key should include period - keyLockParamsRecord(userAddress), - formatLockParamsRecord(userNum, amount, start, duration, k, b, gwxAmount))] +func LockParamsEntry( + userAddress: Address, + txId: ByteVector, + amount: Int, + start: Int, + duration: Int, + gwxAmount: Int, + wxClaimed: Int +) = { + [ + StringEntry( + keyLockParamsRecord(userAddress, txId), + formatLockParamsRecord(amount, start, duration, gwxAmount, wxClaimed) + ) + ] } func extractOptionalPaymentAmountOrFail(i: Invocation, expectedAssetId: ByteVector) = { @@ -537,11 +522,9 @@ func refreshBoostEmissionIntegral() = { } func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly: Boolean) = { - let userRecordOption = this.getString(keyLockParamsRecord(userAddressStr)) - if (userRecordOption == unit) then (0, [], "userRecord::is::empty") else + let userAddress = addressFromString(userAddressStr).valueOrErrorMessage(wrapErr("invalid user address")) - let userRecordArray = userRecordOption.value().split(SEP) - let userNumStr = userRecordArray[IdxLockUserNum] + strict userNum = userNumberByAddressOrFail(userAddress) let EMPTYSTR = "empty" # is used from REST let poolWeight = if (lpAssetIdStr != EMPTYSTR) then { @@ -553,9 +536,9 @@ func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly # BOOST INTEGRAL RECALC # updated by claim - let userLpBoostEmissionLastIntegralKEY = keyUserLpBoostEmissionLastINTEGRAL(userNumStr, lpAssetIdStr) + let userLpBoostEmissionLastIntegralKEY = keyUserLpBoostEmissionLastINTEGRAL(userNum, lpAssetIdStr) # updated by lock - let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNumStr) + let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNum) let userBoostEmissionLastIntegral = this.getInteger(userLpBoostEmissionLastIntegralKEY) .valueOrElse(this.getIntOrZero(userBoostEmissionLastIntegralKEY)) @@ -604,7 +587,8 @@ func lockActions(i: Invocation, duration: Int) = { if (assetId != pmt.assetId.value()) then throwErr("invalid asset is in payment - " + assetIdStr + " is expected") else let nextUserNumKEY = keyNextUserNum() - let userAddressStr = i.caller.toString() + let userAddress = i.caller + let userAddressStr = userAddress.toString() let userIsExisting = getString(keyUser2NumMapping(userAddressStr)).isDefined() let userNumStr = if (userIsExisting) then { @@ -615,16 +599,9 @@ func lockActions(i: Invocation, duration: Int) = { let userNum = userNumStr.parseIntValue() let lockStart = height - let startBlockKEY = keyLockParamStartBlock(userNumStr) - let durationKEY = keyLockParamDuration(userNumStr) - - let userAmountKEY = keyLockParamUserAmount(userNumStr) - - if ((pmtAmount < minLockAmount) && i.caller != lpStakingPoolsContract) then throwErr("amount is less then minLockAmount=" + minLockAmount.toString()) else + if ((pmtAmount < minLockAmount) && userAddress != lpStakingPoolsContract) then throwErr("amount is less then minLockAmount=" + minLockAmount.toString()) else if (duration < minLockDuration) then throwErr("passed duration is less then minLockDuration=" + minLockDuration.toString()) else if (duration > maxLockDuration) then throwErr("passed duration is greater then maxLockDuration=" + maxLockDuration.toString()) else - if (userIsExisting && (this.getIntOrFail(startBlockKEY) + this.getIntOrFail(durationKEY)) >= lockStart) then throwErr("there is an active lock - consider to use increaseLock") else - if (this.getIntOrZero(userAmountKEY) > 0) then throwErr("there are locked WXs - consider to use increaseLock " + userAmountKEY) else let coeffX8 = fraction(duration, MULT8, maxLockDuration) let gWxAmountStart = fraction(pmtAmount, coeffX8, MULT8) @@ -637,7 +614,7 @@ func lockActions(i: Invocation, duration: Int) = { let totalCachedGwxRaw = getGwxTotal() - let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNumStr) + let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNum) let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 let arr = if (userIsExisting) then [] else [ @@ -645,7 +622,7 @@ func lockActions(i: Invocation, duration: Int) = { StringEntry(keyUser2NumMapping(userAddressStr), userNumStr), StringEntry(keyNum2UserMapping(userNumStr), userAddressStr) ] - (arr ++ LockParamsEntry(userAddressStr, userNumStr, pmtAmount, lockStart, duration, k, b, period) + (arr ++ LockParamsEntry(userAddress, i.transactionId, pmtAmount, lockStart, duration, gWxAmountStart, 0) ++ StatsEntry(pmtAmount, duration, 1, if (userIsExisting) then 0 else 1) :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, duration, k, b, i) ++ [ @@ -687,60 +664,6 @@ func lock(duration: Int) = { (lockActionsResult, unit) } -@Callable(i) -func increaseLock(deltaDuration: Int) = { - let cfgArray = readConfigArrayOrFail() - let assetIdStr = cfgArray[IdxCfgAssetId] - let assetId = assetIdStr.fromBase58String() - let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseIntValue() - let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() - - let pmtAmount = extractOptionalPaymentAmountOrFail(i, assetId) - - let userAddress = i.caller - let userAddressStr = userAddress.toString() - let userRecordArray = readLockParamsRecordOrFail(userAddressStr) - - let userNumStr = userRecordArray[IdxLockUserNum] - let userAmount = userRecordArray[IdxLockAmount].parseIntValue() - let lockStart = userRecordArray[IdxLockStart].parseIntValue() - let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() - let lockEnd = lockStart + lockDuration - let remainingDuration = max([lockEnd - height, 0]) - - let userAmountNew = userAmount + pmtAmount - let lockDurationNew = remainingDuration + deltaDuration - - if (deltaDuration < 0) then throwErr("duration is less then zero") else - if (lockDurationNew < minLockDuration) then throwErr("lockDurationNew is less then minLockDuration=" + minLockDuration.toString()) else - if (lockDurationNew > maxLockDuration) then throwErr("deltaDuration + existedLockDuration is greater then maxLockDuration=" + maxLockDuration.toString()) else - #if (lockEnd <= height && userAmount > 0) then throwErr("there is an expired lock - need to unlock before new lock") else - - let coeffX8 = fraction(lockDurationNew, MULT8, maxLockDuration) - let gWxAmountStart = fraction(userAmountNew, coeffX8, MULT8) - - strict updateRefActivity = mathContract.invoke("updateReferralActivity", [i.caller.toString(), gWxAmountStart], []) - - let lockStartNew = height - let gWxParamsResultList = invoke(mathContract, "calcGwxParamsREADONLY", [gWxAmountStart, lockStartNew, lockDurationNew], []).exactAs[List[Any]] - # TODO check the following macros: gWxParamsInvokeResult[0].exactAs[Int] - let k = gWxParamsResultList[0].exactAs[Int] - let b = gWxParamsResultList[1].exactAs[Int] - let period = gWxParamsResultList[2].exactAs[Int].toString() - - let currUserGwx = getGwxAmount(userAddress) - let gwxDiff = gWxAmountStart - currUserGwx - if (gwxDiff < 0) then throwErr("gwxDiff is less then 0: " + gwxDiff.toString()) else - let totalCachedGwxRaw = getGwxTotal() - - LockParamsEntry(userAddressStr, userNumStr, userAmountNew, lockStartNew, lockDurationNew, k, b, period) - ++ StatsEntry(pmtAmount, deltaDuration, 0, 0) - :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, lockDurationNew, k, b, i) - ++ [ - IntegerEntry(keyGwxTotal(), totalCachedGwxRaw + gwxDiff) - ] -} - @Callable(i) func claimWxBoost(lpAssetIdStr: String, userAddressStr: String) = { if (stakingContract != i.caller) then throwErr("permissions denied") else @@ -756,18 +679,18 @@ func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { } @Callable(i) -func unlock(userAddressStr: String, amount: Int) = { +func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { let userAddress = userAddressStr.addressFromString() .valueOrErrorMessage(wrapErr("invalid user address")) - let userRecordArray = readLockParamsRecordOrFail(userAddressStr) + let txId = txIdStr.fromBase58String() + let userRecordArray = readLockParamsRecordOrFail(userAddress, txId) - let userNumStr = userRecordArray[IdxLockUserNum] let userAmount = userRecordArray[IdxLockAmount].parseIntValue() let lockStart = userRecordArray[IdxLockStart].parseIntValue() let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() let lockEnd = lockStart + lockDuration # TODO: save claimed amount - let userClaimed = 0 + let wxClaimed = 0 # TODO: gwx amount let gwxAmount = 0 @@ -779,7 +702,7 @@ func unlock(userAddressStr: String, amount: Int) = { lockDuration ) let wxWithdrawable = if (height > lockEnd) then { - userAmount - userClaimed + userAmount - wxClaimed } else { fraction( userAmount, @@ -810,7 +733,7 @@ func unlock(userAddressStr: String, amount: Int) = { let period = mathContract.getInteger(keyNextPergetIntOrDefault()).valueOrElse(0) - LockParamsEntry(userAddressStr, userNumStr, 0, lockStart, lockDuration, 0, 0, period.toString()) + LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + amount) ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, 0, 0, i) :+ ScriptTransfer(userAddress, userAmount, assetId) @@ -826,23 +749,10 @@ func gwxUserInfoREADONLY(userAddressStr: String) = { } # for lp_staking_pools +# TODO: check if it works correctly @Callable(i) func userMaxDurationREADONLY(userAddressStr: String) = { - let cfgArray = readConfigArrayOrFail() - let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() - - let userRecordOption = this.getString(userAddressStr.keyLockParamsRecord()) - if (userRecordOption == unit) then ([], ("lock", maxLockDuration)) else { - let userRecordArray = userRecordOption.value().split(SEP) - let lockStart = userRecordArray[IdxLockStart].parseIntValue() - let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() - let lockEnd = lockStart + lockDuration - let remainingDuration = max([lockEnd - height, 0]) - - let maxDeltaDuration = maxLockDuration - remainingDuration - - ([], ("increaseLock", maxDeltaDuration)) - } + (nil, maxLockDurationX8 / MULT8) } # TODO: replace by getUserGwxAmountREADONLY (in all contracts) From 2d2d9102b4f39bf6710ad3883acadca664707aeb Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:19:57 +0500 Subject: [PATCH 07/86] fix keyUserGwxAmountTotal --- ride/boosting_v2.ride | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 2a2a926fb..21c926ec2 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -154,7 +154,7 @@ let IdxLockParamWxClaimed = 6 func keyLockParamsRecord(userAddress: Address, txId: ByteVector) = makeString(["%s%s%s__lock", userAddress.toString(), txId.toBase58String()], SEP) func readLockParamsRecordOrFail(userAddress: Address, txId: ByteVector) = this.getStringOrFail(keyLockParamsRecord(userAddress, txId)).split(SEP) -func keyUserGwxAmountTotal(userAddress: Address) = makeString(["%s%s%s__gwxAmountTotal", userAddress.toString()], SEP) +func keyUserGwxAmountTotal(userAddress: Address) = makeString(["%s%s__gwxAmountTotal", userAddress.toString()], SEP) func formatLockParamsRecord( amount: Int, From edbdbebdd3b19a41698b4d172148d207b3f0462f Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:51:48 +0500 Subject: [PATCH 08/86] update gwx amount in lock/unlock --- ride/boosting_v2.ride | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 21c926ec2..de50a6a0a 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -259,7 +259,7 @@ func userNumberByAddressOrFail(userAddress: Address) = { } } -func getGwxTotal() = { +func getGwxAmountTotal() = { this.getInteger(keyGwxTotal()).valueOrElse(0) } @@ -346,8 +346,8 @@ func extractOptionalPaymentAmountOrFail(i: Invocation, expectedAssetId: ByteVect } # TODO: parse state and return user's current gwx amount -func getGwxAmount(userAddress: Address) = { - 0 +func getUserGwxAmountTotal(userAddress: Address) = { + this.getInteger(keyUserGwxAmountTotal(userAddress)).valueOrElse(0) } func getVotingEmissionEpochInfo() = { @@ -612,11 +612,13 @@ func lockActions(i: Invocation, duration: Int) = { let b = gWxParamsResultList[1].exactAs[Int] let period = gWxParamsResultList[2].exactAs[Int].toString() - let totalCachedGwxRaw = getGwxTotal() + let gwxAmountTotal = getGwxAmountTotal() let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNum) let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 + let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) + let arr = if (userIsExisting) then [] else [ IntegerEntry(nextUserNumKEY, userNum + 1), StringEntry(keyUser2NumMapping(userAddressStr), userNumStr), @@ -627,7 +629,8 @@ func lockActions(i: Invocation, duration: Int) = { :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, duration, k, b, i) ++ [ IntegerEntry(userBoostEmissionLastIntegralKEY, boostEmissionIntegral), - IntegerEntry(keyGwxTotal(), totalCachedGwxRaw + gWxAmountStart) + IntegerEntry(keyGwxTotal(), gwxAmountTotal + gWxAmountStart), + IntegerEntry(keyUserGwxAmountTotal(userAddress), userGwxAmountTotal + gWxAmountStart) ], gWxAmountStart) } @@ -733,17 +736,25 @@ func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { let period = mathContract.getInteger(keyNextPergetIntOrDefault()).valueOrElse(0) + let gwxAmountTotal = getGwxAmountTotal() + let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) + LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + amount) ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, 0, 0, i) :+ ScriptTransfer(userAddress, userAmount, assetId) + ++ [ + # TODO: refresh userBoostEmissionLastIntegralKEY? + IntegerEntry(keyGwxTotal(), ensurePositive(gwxAmountTotal - gwxBurned, "gwxTotal")), + IntegerEntry(keyUserGwxAmountTotal(userAddress), ensurePositive(userGwxAmountTotal - gwxBurned, "userGwxAmountTotal")) + ] } @Callable(i) func gwxUserInfoREADONLY(userAddressStr: String) = { let userAddress = userAddressStr.addressFromString() .valueOrErrorMessage(wrapErr("invalid user address")) - let gwxAmount = getGwxAmount(userAddress) + let gwxAmount = getUserGwxAmountTotal(userAddress) ([], [gwxAmount]) } @@ -760,14 +771,14 @@ func userMaxDurationREADONLY(userAddressStr: String) = { func getUserGwxAmountAtHeightREADONLY(userAddressStr: String) = { let userAddress = userAddressStr.addressFromString() .valueOrErrorMessage(wrapErr("invalid user address")) - let gwxAmount = userAddress.getGwxAmount() + let gwxAmount = userAddress.getUserGwxAmountTotal() ([], gwxAmount) } @Callable(i) func getGwxTotalREADONLY() = { - ([], getGwxTotal()) + ([], getGwxAmountTotal()) } # call this function when wxEmissionRate or boostCoeff changes From a029b06f0f7588c36a47c92b1a3d3a8983648f4e Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 18 Jul 2023 13:48:09 +0500 Subject: [PATCH 09/86] fix HistoryEntry --- ride/boosting_v2.ride | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index de50a6a0a..4436b413e 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -280,18 +280,17 @@ func getLockedGwxAmount(userAddress: Address) = { locked } -func HistoryEntry(type: String, user: String, amount: Int, lockStart: Int, duration: Int, k: Int, b: Int, i: Invocation) = { +func HistoryEntry(type: String, user: String, amount: Int, lockStart: Int, duration: Int, gwxAmount: Int, i: Invocation) = { let historyKEY = makeString(["%s%s%s%s__history", type, user, i.transactionId.toBase58String()], SEP) let historyDATA = makeString([ - "%d%d%d%d%d%d%d", - lastBlock.height.toString(), - lastBlock.timestamp.toString(), - amount.toString(), - lockStart.toString(), - duration.toString(), - k.toString(), - b.toString()], - SEP) + "%d%d%d%d%d%d%d", + lastBlock.height.toString(), + lastBlock.timestamp.toString(), + amount.toString(), + lockStart.toString(), + duration.toString(), + gwxAmount.toString() + ], SEP) StringEntry(historyKEY, historyDATA) } @@ -626,7 +625,7 @@ func lockActions(i: Invocation, duration: Int) = { ] (arr ++ LockParamsEntry(userAddress, i.transactionId, pmtAmount, lockStart, duration, gWxAmountStart, 0) ++ StatsEntry(pmtAmount, duration, 1, if (userIsExisting) then 0 else 1) - :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, duration, k, b, i) + :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, duration, gWxAmountStart, i) ++ [ IntegerEntry(userBoostEmissionLastIntegralKEY, boostEmissionIntegral), IntegerEntry(keyGwxTotal(), gwxAmountTotal + gWxAmountStart), @@ -741,7 +740,7 @@ func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + amount) ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed - :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, 0, 0, i) + :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, gwxBurned, i) :+ ScriptTransfer(userAddress, userAmount, assetId) ++ [ # TODO: refresh userBoostEmissionLastIntegralKEY? From 814bdfad79ccbff32bd4085509873bc83177619c Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 18 Jul 2023 14:01:05 +0500 Subject: [PATCH 10/86] remove calcGwxAmountStart --- ride/boosting_v2.ride | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 4436b413e..e1ebf28df 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -311,14 +311,6 @@ func StatsEntry(totalLockedInc: Int, durationInc: Int, lockCountInc: Int, usersC IntegerEntry(totalAmountKEY, totalAmount + totalLockedInc)] } -func calcGwxAmountStart(amount: Int, duration: Int) = { - fraction( - amount, - duration * MULT8, - maxLockDurationX8 - ) -} - func LockParamsEntry( userAddress: Address, txId: ByteVector, From 206ad3dadad31c5dc522b85eb71ffecad08fdb85 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 18 Jul 2023 14:10:34 +0500 Subject: [PATCH 11/86] use maxLockDuration from config --- ride/boosting_v2.ride | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index e1ebf28df..3824ed4cc 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -16,11 +16,6 @@ let POOLWEIGHTMULT = MULT8 let contractFilename = "boosting.ride" let blocksInDay = 24 * 60 -let daysInYearX8 = fraction(3652425, MULT8, 10000) -let blocksInYearX8 = daysInYearX8 * blocksInDay -let monthsInYear = 12 -let blockInMonthX8 = blocksInYearX8 / monthsInYear -let maxLockDurationX8 = 4 * blocksInYearX8 func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") func throwErr(msg: String) = msg.wrapErr().throw() @@ -96,7 +91,12 @@ let IdxCfgMathContract = 5 func keyConfig() = {"%s__config"} func readConfigArrayOrFail() = this.getStringOrFail(keyConfig()).split(SEP) -let mathContract = readConfigArrayOrFail()[IdxCfgMathContract].addressFromStringValue() +let cfgArray = readConfigArrayOrFail() +let assetId = cfgArray[IdxCfgAssetId].fromBase58String() +let minLockAmount = cfgArray[IdxCfgMinLockAmount].parseInt().valueOrErrorMessage(wrapErr("invalid min lock amount")) +let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseInt().valueOrErrorMessage(wrapErr("invalid min lock duration")) +let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseInt().valueOrErrorMessage(wrapErr("invalid max lock duration")) +let mathContract = cfgArray[IdxCfgMathContract].addressFromString().valueOrErrorMessage(wrapErr("invalid math contract address")) func formatConfigS(assetId: String, minLockAmount: String, minLockDuration: String, maxLockDuration: String, mathContract: String) = { makeString([ @@ -564,13 +564,7 @@ func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly } func lockActions(i: Invocation, duration: Int) = { - let cfgArray = readConfigArrayOrFail() - let assetIdStr = cfgArray[IdxCfgAssetId] - let assetId = assetIdStr.fromBase58String() - let minLockAmount = cfgArray[IdxCfgMinLockAmount].parseIntValue() - let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseIntValue() - let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() - + let assetIdStr = assetId.toBase58String() if (i.payments.size() != 1) then throwErr("invalid payment - exact one payment must be attached") else let pmt = i.payments[0] let pmtAmount = pmt.amount @@ -711,7 +705,7 @@ func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { let gwxBurned = max([ amount, - fraction(t, userAmount * MULT8, 4 * daysInYearX8) + fraction(t * blocksInDay, userAmount * MULT8, maxLockDuration) ]) let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") let lockedGwxAmount = getLockedGwxAmount(userAddress) @@ -720,9 +714,6 @@ func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { then throwErr("locked gwx amount: " + lockedGwxAmount.toString()) else - let cfgArray = readConfigArrayOrFail() - let assetId = cfgArray[IdxCfgAssetId].fromBase58String() - if (userAmount <= 0) then throwErr("nothing to unlock") else let period = mathContract.getInteger(keyNextPergetIntOrDefault()).valueOrElse(0) @@ -754,7 +745,7 @@ func gwxUserInfoREADONLY(userAddressStr: String) = { # TODO: check if it works correctly @Callable(i) func userMaxDurationREADONLY(userAddressStr: String) = { - (nil, maxLockDurationX8 / MULT8) + (nil, maxLockDuration) } # TODO: replace by getUserGwxAmountREADONLY (in all contracts) From 77f64ed9bf0ec6fdfb83143afc903f2049c4d7ca Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 18 Jul 2023 14:45:09 +0500 Subject: [PATCH 12/86] fix gwxBurned --- ride/boosting_v2.ride | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 3824ed4cc..803053d42 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -705,7 +705,7 @@ func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { let gwxBurned = max([ amount, - fraction(t * blocksInDay, userAmount * MULT8, maxLockDuration) + fraction(t * blocksInDay, userAmount, maxLockDuration) ]) let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") let lockedGwxAmount = getLockedGwxAmount(userAddress) From 5c1c93865a68ab947d7d9729de45edb536fa57ed Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 18 Jul 2023 14:50:03 +0500 Subject: [PATCH 13/86] fix lock param indeces --- ride/boosting_v2.ride | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 803053d42..8d256d42e 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -147,9 +147,9 @@ func mustManager(i: Invocation) = { let IdxLockAmount = 1 let IdxLockStart = 2 let IdxLockDuration = 3 -let IdxLockParamLastUpdateTimestamp = 4 -let IdxLockParamGwxAmount = 5 -let IdxLockParamWxClaimed = 6 +let IdxLockLastUpdateTimestamp = 4 +let IdxLockGwxAmount = 5 +let IdxLockWxClaimed = 6 func keyLockParamsRecord(userAddress: Address, txId: ByteVector) = makeString(["%s%s%s__lock", userAddress.toString(), txId.toBase58String()], SEP) func readLockParamsRecordOrFail(userAddress: Address, txId: ByteVector) = this.getStringOrFail(keyLockParamsRecord(userAddress, txId)).split(SEP) From ba5db259a40a619b80685cb998e63d8390762a7a Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 18 Jul 2023 15:26:44 +0500 Subject: [PATCH 14/86] wxClaimed and gwxAmount in unlock --- ride/boosting_v2.ride | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 8d256d42e..63ce9a796 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -677,10 +677,8 @@ func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { let lockStart = userRecordArray[IdxLockStart].parseIntValue() let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() let lockEnd = lockStart + lockDuration - # TODO: save claimed amount - let wxClaimed = 0 - # TODO: gwx amount - let gwxAmount = 0 + let wxClaimed = userRecordArray[IdxLockWxClaimed].parseIntValue() + let gwxAmount = userRecordArray[IdxLockGwxAmount].parseIntValue() let t = (height - lockStart) / blocksInDay From 857303b51b6470a7cea8f1ad1ed506d9eb819b7c Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 18 Jul 2023 15:40:57 +0500 Subject: [PATCH 15/86] remove getUserGwxAmountTotal todo comment --- ride/boosting_v2.ride | 1 - 1 file changed, 1 deletion(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 63ce9a796..5f24e24ac 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -336,7 +336,6 @@ func extractOptionalPaymentAmountOrFail(i: Invocation, expectedAssetId: ByteVect pmt.amount } -# TODO: parse state and return user's current gwx amount func getUserGwxAmountTotal(userAddress: Address) = { this.getInteger(keyUserGwxAmountTotal(userAddress)).valueOrElse(0) } From e63fef70fb390338ceead42a5f83c4bd272628ea Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:56:15 +0500 Subject: [PATCH 16/86] remove calcGwxParamsREADONLY usage --- ride/boosting_v2.ride | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 5f24e24ac..9467f77f9 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -179,7 +179,6 @@ func keyNextUserNum() = "%s__nextUserNum" func keyUser2NumMapping(userAddress: String) = makeString(["%s%s%s__mapping__user2num", userAddress], SEP) func keyNum2UserMapping(num: String) = makeString(["%s%s%s__mapping__num2user", num], SEP) -# TODO - consider to concat func keyLockParamTotalAmount() = "%s%s__stats__activeTotalLocked" func keyStatsLocksDurationSumInBlocks() = "%s%s__stats__locksDurationSumInBlocks" func keyStatsLocksCount() = "%s%s__stats__locksCount" @@ -590,12 +589,6 @@ func lockActions(i: Invocation, duration: Int) = { let coeffX8 = fraction(duration, MULT8, maxLockDuration) let gWxAmountStart = fraction(pmtAmount, coeffX8, MULT8) - let gWxParamsResultList = invoke(mathContract, "calcGwxParamsREADONLY", [gWxAmountStart, lockStart, duration], []).exactAs[List[Any]] - # TODO check the following macros: gWxParamsInvokeResult[0].exactAs[Int] - let k = gWxParamsResultList[0].exactAs[Int] - let b = gWxParamsResultList[1].exactAs[Int] - let period = gWxParamsResultList[2].exactAs[Int].toString() - let gwxAmountTotal = getGwxAmountTotal() let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNum) From 32b4c763d6aecd9fcbbc8ca90e9e8c85df82e2f7 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 19 Jul 2023 12:13:04 +0500 Subject: [PATCH 17/86] fix getUserGwxAmountAtHeightREADONLY --- ride/boosting_v2.ride | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 9467f77f9..332443b1e 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -716,7 +716,6 @@ func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, gwxBurned, i) :+ ScriptTransfer(userAddress, userAmount, assetId) ++ [ - # TODO: refresh userBoostEmissionLastIntegralKEY? IntegerEntry(keyGwxTotal(), ensurePositive(gwxAmountTotal - gwxBurned, "gwxTotal")), IntegerEntry(keyUserGwxAmountTotal(userAddress), ensurePositive(userGwxAmountTotal - gwxBurned, "userGwxAmountTotal")) ] @@ -738,9 +737,9 @@ func userMaxDurationREADONLY(userAddressStr: String) = { (nil, maxLockDuration) } -# TODO: replace by getUserGwxAmountREADONLY (in all contracts) +# targetHeight is unused @Callable(i) -func getUserGwxAmountAtHeightREADONLY(userAddressStr: String) = { +func getUserGwxAmountAtHeightREADONLY(userAddressStr: String, targetHeight: Int) = { let userAddress = userAddressStr.addressFromString() .valueOrErrorMessage(wrapErr("invalid user address")) let gwxAmount = userAddress.getUserGwxAmountTotal() From add5b8521bed82e6ddc4c18258c0e50fd3016ca4 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 19 Jul 2023 12:16:16 +0500 Subject: [PATCH 18/86] fix userMaxDurationREADONLY --- ride/boosting_v2.ride | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 332443b1e..1b9cfd771 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -734,7 +734,7 @@ func gwxUserInfoREADONLY(userAddressStr: String) = { # TODO: check if it works correctly @Callable(i) func userMaxDurationREADONLY(userAddressStr: String) = { - (nil, maxLockDuration) + ([], ("increaseLock", maxLockDuration)) } # targetHeight is unused From 5b9bf9555c7c8ea445676be4c13535254e51d0a0 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 19 Jul 2023 12:16:59 +0500 Subject: [PATCH 19/86] remove todo --- ride/boosting_v2.ride | 1 - 1 file changed, 1 deletion(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 1b9cfd771..8700709f4 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -731,7 +731,6 @@ func gwxUserInfoREADONLY(userAddressStr: String) = { } # for lp_staking_pools -# TODO: check if it works correctly @Callable(i) func userMaxDurationREADONLY(userAddressStr: String) = { ([], ("increaseLock", maxLockDuration)) From 2fee1178b3aa22c03a7c686c49d8e8bcdff87e4c Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 19 Jul 2023 15:19:36 +0500 Subject: [PATCH 20/86] remove period --- ride/boosting_v2.ride | 2 -- 1 file changed, 2 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 8700709f4..92920b116 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -706,8 +706,6 @@ func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { if (userAmount <= 0) then throwErr("nothing to unlock") else - let period = mathContract.getInteger(keyNextPergetIntOrDefault()).valueOrElse(0) - let gwxAmountTotal = getGwxAmountTotal() let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) From 0ea9ae71fcfcf6aacd5badf76d2292f6e872d1b0 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:39:29 +0500 Subject: [PATCH 21/86] lock duration --- ride/boosting_v2.ride | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 92920b116..31c8a5dd5 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -15,7 +15,10 @@ let MULT8 = 100000000 let POOLWEIGHTMULT = MULT8 let contractFilename = "boosting.ride" -let blocksInDay = 24 * 60 +# 24 * 60 +let blocksInDay = 1440 +# 365 * 24 * 60 / 12 +let blocksInMonth = 43800 func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") func throwErr(msg: String) = msg.wrapErr().throw() @@ -561,7 +564,10 @@ func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly (userBoostAvaliableToClaimTotalNew, dataState, debug) } -func lockActions(i: Invocation, duration: Int) = { +func lockActions(i: Invocation, durationMonths: Int) = { + let durationMonthsAllowed = [1, 3, 6, 12, 24, 48] + if (!durationMonthsAllowed.containsElement(durationMonths)) then throwErr("invalid duration") else + let duration = durationMonths * blocksInMonth let assetIdStr = assetId.toBase58String() if (i.payments.size() != 1) then throwErr("invalid payment - exact one payment must be attached") else let pmt = i.payments[0] From 410cddb6343d15366e6e25f0d14c914e25c953ef Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 21 Jul 2023 19:20:28 +0500 Subject: [PATCH 22/86] proposal getLockedGwxAmount --- ride/proposal.ride | 63 +++++++++++++- test/components/proposal/_hooks.mjs | 58 +++++++++++++ .../components/proposal/contract/proposal.mjs | 54 ++++++++++++ .../proposal/get_locked_gwx_amount.spec.mjs | 86 +++++++++++++++++++ .../proposal/mock/gwx_reward.mock.ride | 19 ++++ test/package.json | 1 + 6 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 test/components/proposal/_hooks.mjs create mode 100644 test/components/proposal/contract/proposal.mjs create mode 100644 test/components/proposal/get_locked_gwx_amount.spec.mjs create mode 100644 test/components/proposal/mock/gwx_reward.mock.ride diff --git a/ride/proposal.ride b/ride/proposal.ride index 7c5f0f1f6..56d8058a7 100644 --- a/ride/proposal.ride +++ b/ride/proposal.ride @@ -3,6 +3,10 @@ {-# SCRIPT_TYPE ACCOUNT #-} let SEP = "__" +let contractFilename = "proposal.ride" + +func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") +func throwErr(msg: String) = msg.wrapErr().throw() func blockHeightError() = "The block's height is too big for this proposal".throw() func alreadyVoteError() = "You have already voted".throw() @@ -19,6 +23,12 @@ func keyUserVoteOnProposal(number: Int, user: String) = { ["%s%d%s", "userVoteOnProposal", number.toString(), user].makeString(SEP) } +func keyActiveVotingList() = ["%s", "activeVotingList"].makeString(SEP) +func getActiveVotingList() = match this.getString(keyActiveVotingList()) { + case s: String => s.split(SEP) + case _: Unit => [] +} + func keyManagerPublicKey() = "%s__managerPublicKey" func keyManagerVaultAddress() = "%s__managerVaultAddress" @@ -29,6 +39,13 @@ func getManagerVaultAddressOrThis() = { case _=> this } } + +let idxProposalInfoName = 1 +let idxProposalInfoEnd = 2 +let idxProposalInfoQuorumNumber = 3 +let idxProposalInfoPositiveVotes = 4 +let idxProposalInfoNegativeVotes = 5 + func formatProposalInfo( name: String, end: String, @@ -47,6 +64,9 @@ func formatProposalInfo( SEP) } +let idxUserVoteOnProposalVote = 1 +let idxUserVoteOnProposalGwxNumber = 2 + func formatUserVoteOnProposal(vote: String, gwxNumber: String) = { makeString([ "%d%d", # 0 @@ -88,6 +108,7 @@ func startNewVote(name: String, description: String, expirationHeight: Int, quor i.mustManager() ] let theIndex = getCurrentIndex() + let newActiveVotingList = theIndex.toString() :: getActiveVotingList() [ IntegerEntry(keyCurrentIndex(), theIndex + 1), @@ -102,10 +123,12 @@ func startNewVote(name: String, description: String, expirationHeight: Int, quor "0" ) ), - StringEntry(keyProposalDescription(theIndex), description) + StringEntry(keyProposalDescription(theIndex), description), + StringEntry(keyActiveVotingList(), newActiveVotingList.makeString(SEP)) ] } +# TODO: refresh active voting list @Callable(i) func voteFor(proposalIndex: Int, choice: Boolean) = { let EMPTY = "EMPTY" @@ -170,6 +193,7 @@ func voteFor(proposalIndex: Int, choice: Boolean) = { ] } +# TODO: refresh active voting list @Callable(i) func deleteVote(proposalIndex: Int) = { let EMPTY = "EMPTY" @@ -212,6 +236,7 @@ func deleteVote(proposalIndex: Int) = { ] } +# TODO: refresh active voting list @Callable(i) func changeVote(proposalIndex: Int, choice: Boolean) = { let EMPTY = "EMPTY" @@ -295,6 +320,42 @@ func getResultREADONLY(proposalIndex: Int) = { ([], [positiveVotes, negativeVotes, quorumNumber]) } +@Callable(i) +func getLockedGwxAmount(userAddressStr: String) = { + let userAddress = addressFromString(userAddressStr) + .valueOrErrorMessage(wrapErr("invalid address")) + let activeVotingList = getActiveVotingList() + func map(acc: List[Int], proposalIndexStr: String) = { + let proposalIndex = proposalIndexStr.parseInt().value() + let votingInfo = this.getString(keyProposalInfo(proposalIndex)) + .valueOrErrorMessage(wrapErr("invalid proposal index in active voting list")) + let votingInfoArray = votingInfo.split(SEP) + let votingEndHeight = votingInfoArray[idxProposalInfoEnd].parseInt().value() + let votingIsOver = lastBlock.height > votingEndHeight + let userVoteInfoOption = this.getString( + keyUserVoteOnProposal(proposalIndex, userAddressStr) + ) + let userVoteGwxAmount = match userVoteInfoOption { + case userVoteInfo: String => { + let userVoteInfoArray = userVoteInfo.split(SEP) + userVoteInfoArray[idxUserVoteOnProposalGwxNumber].parseInt().value() + } + case _: Unit => 0 + } + let activeVoteValue = if (votingIsOver) then { + 0 + } else { + userVoteGwxAmount + } + + activeVoteValue :: acc + } + let userVotes = FOLD<30>(activeVotingList, [], map) + let maxUserVote = max(userVotes) + + (nil, maxUserVote) +} + @Verifier(tx) func verify() = { let targetPublicKey = match managerPublicKeyOrUnit() { diff --git a/test/components/proposal/_hooks.mjs b/test/components/proposal/_hooks.mjs new file mode 100644 index 000000000..e3e3f84c9 --- /dev/null +++ b/test/components/proposal/_hooks.mjs @@ -0,0 +1,58 @@ +import wc from '@waves/ts-lib-crypto'; +import { + massTransfer, data, +} from '@waves/waves-transactions'; +import { table, getBorderCharacters } from 'table'; +import { format } from 'path'; +import { setScriptFromFile } from '../../utils/utils.mjs'; +import { broadcastAndWait, chainId, baseSeed } from '../../utils/api.mjs'; + +const nonceLength = 3; + +const ridePath = '../ride'; +const mockPath = './components/proposal/mock'; +const gwxRewardMockPath = format({ dir: mockPath, base: 'gwx_reward.mock.ride' }); +const proposalPath = format({ dir: ridePath, base: 'proposal.ride' }); + +export const mochaHooks = { + async beforeAll() { + const nonce = wc.random(nonceLength, 'Buffer').toString('hex'); + const contractNames = [ + 'gwxReward', + 'proposal', + ]; + const userNames = Array.from({ length: 3 }, (_, k) => `user${k}`); + const names = [...contractNames, ...userNames, 'pacemaker']; + this.accounts = Object.fromEntries(names.map((item) => { + const seed = `${item}#${nonce}`; + return [item, { seed, address: wc.address(seed, chainId), publicKey: wc.publicKey(seed) }]; + })); + const amount = 1e10; + await broadcastAndWait(massTransfer({ + transfers: Object.values(this.accounts) + .map(({ address: recipient }) => ({ recipient, amount })), + chainId, + }, baseSeed)); + + await broadcastAndWait(data({ + data: [ + { key: '%s__currentIndex', type: 'integer', value: 0 }, + { key: '%s__gwxContractAddress', type: 'string', value: this.accounts.gwxReward.address }, + ], + chainId, + }, this.accounts.proposal.seed)); + + await Promise.all([ + setScriptFromFile(gwxRewardMockPath, this.accounts.gwxReward.seed), + setScriptFromFile(proposalPath, this.accounts.proposal.seed), + ]); + + const accountsInfo = Object.entries(this.accounts) + .map(([name, { seed, address }]) => [name, seed, wc.privateKey(seed), address]); + console.log(table(accountsInfo, { + border: getBorderCharacters('norc'), + drawHorizontalLine: (index, size) => index === 0 || index === 1 || index === size, + header: { content: `pid = ${process.pid}, nonce = ${nonce}` }, + })); + }, +}; diff --git a/test/components/proposal/contract/proposal.mjs b/test/components/proposal/contract/proposal.mjs new file mode 100644 index 000000000..8df289052 --- /dev/null +++ b/test/components/proposal/contract/proposal.mjs @@ -0,0 +1,54 @@ +import { invokeScript } from '@waves/waves-transactions'; +import { broadcastAndWait, chainId } from '../../../utils/api.mjs'; + +export const proposal = { + startNewVote: async ({ + dApp, caller, payments = [], + name, description, duration, quorumNumber, + }) => { + const invokeTx = invokeScript( + { + dApp, + call: { + function: 'startNewVote', + args: [ + { type: 'string', value: name }, + { type: 'string', value: description }, + { type: 'integer', value: duration }, + { type: 'integer', value: quorumNumber }, + ], + }, + payment: payments, + additionalFee: 4e5, + chainId, + }, + caller, + ); + + return broadcastAndWait(invokeTx); + }, + + voteFor: async ({ + dApp, caller, payments = [], + proposalIndex, choice, + }) => { + const invokeTx = invokeScript( + { + dApp, + call: { + function: 'voteFor', + args: [ + { type: 'integer', value: proposalIndex }, + { type: 'boolean', value: choice }, + ], + }, + payment: payments, + additionalFee: 4e5, + chainId, + }, + caller, + ); + + return broadcastAndWait(invokeTx); + }, +}; diff --git a/test/components/proposal/get_locked_gwx_amount.spec.mjs b/test/components/proposal/get_locked_gwx_amount.spec.mjs new file mode 100644 index 000000000..631475066 --- /dev/null +++ b/test/components/proposal/get_locked_gwx_amount.spec.mjs @@ -0,0 +1,86 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { data } from '@waves/waves-transactions'; +import { api, broadcastAndWait, chainId } from '../../utils/api.mjs'; +import { proposal } from './contract/proposal.mjs'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +describe(`${process.pid}: proposal`, () => { + it('get locked gwx amount should work', async function () { + const duration = 60; + const quorumNumber = 1e8; + await Promise.all([ + proposal.startNewVote({ + dApp: this.accounts.proposal.address, + caller: this.accounts.proposal.seed, + name: 'test1', + description: 'test1', + duration, + quorumNumber, + }), + proposal.startNewVote({ + dApp: this.accounts.proposal.address, + caller: this.accounts.proposal.seed, + name: 'test2', + description: 'test2', + duration, + quorumNumber, + }), + proposal.startNewVote({ + dApp: this.accounts.proposal.address, + caller: this.accounts.proposal.seed, + name: 'test3', + description: 'test3', + duration, + quorumNumber, + }), + ]); + + const vote1 = 1e8; + await broadcastAndWait(data({ + data: [ + { + key: `%s%s__gwxAmountTotal__${this.accounts.user0.address}`, + value: vote1, + }, + ], + chainId, + }, this.accounts.gwxReward.seed)); + + await proposal.voteFor({ + dApp: this.accounts.proposal.address, + caller: this.accounts.user0.seed, + proposalIndex: 0, + choice: true, + }); + + const vote2 = 2e8; + await broadcastAndWait(data({ + data: [ + { + key: `%s%s__gwxAmountTotal__${this.accounts.user0.address}`, + value: vote2, + }, + ], + chainId, + }, this.accounts.gwxReward.seed)); + + await proposal.voteFor({ + dApp: this.accounts.proposal.address, + caller: this.accounts.user0.seed, + proposalIndex: 2, + choice: true, + }); + + const expr = `getLockedGwxAmount("${this.accounts.user0.address}")`; + const response = await api.utils.fetchEvaluate( + this.accounts.proposal.address, + expr, + ); + const result = response.result.value._2.value; + + expect(result).to.equal(Math.max(vote1, vote2)); + }); +}); diff --git a/test/components/proposal/mock/gwx_reward.mock.ride b/test/components/proposal/mock/gwx_reward.mock.ride new file mode 100644 index 000000000..92afa2e71 --- /dev/null +++ b/test/components/proposal/mock/gwx_reward.mock.ride @@ -0,0 +1,19 @@ +{-# STDLIB_VERSION 6 #-} +{-# CONTENT_TYPE DAPP #-} +{-# SCRIPT_TYPE ACCOUNT #-} + +let SEP = "__" + +func keyUserGwxAmountTotal(userAddress: Address) = makeString(["%s%s__gwxAmountTotal", userAddress.toString()], SEP) + +@Callable(i) +func getUserGwxAmountAtHeightREADONLY( + userAddressStr: String, + targetHeightUnused: Int +) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage("invalid user address") + let gwxAmount = this.getInteger(keyUserGwxAmountTotal(userAddress)).valueOrElse(0) + + ([], gwxAmount) +} diff --git a/test/package.json b/test/package.json index 12dfa403c..d3c328033 100644 --- a/test/package.json +++ b/test/package.json @@ -27,6 +27,7 @@ "test-lp-staking-pools": "mocha -r dotenv/config --parallel --require components/lp_staking_pools/_hooks.mjs components/lp_staking_pools", "test-staking-boosting": "mocha -r dotenv/config --parallel --require components/staking_boosting/_hooks.mjs components/staking_boosting", "test-gwx-reward": "mocha -r dotenv/config --parallel --require components/gwx_reward/_hooks.mjs components/gwx_reward", + "test-proposal": "mocha -r dotenv/config --parallel --require components/proposal/_hooks.mjs components/proposal", "ci-test": "node_modules/.bin/concurrently --max-processes 8 --timings npm:test-*", "exchange-test": "mocha -r dotenv/config --parallel --require components/exchange/_hooks.mjs components/exchange", "test": "API_NODE_URL=http://localhost:6869 npm run ci-test", From 77f91fd4720590a1cc88c1c5af43a61a9c356a40 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 25 Jul 2023 18:38:54 +0500 Subject: [PATCH 23/86] updateActiveVotingListActions + test --- ride/proposal.ride | 35 ++++++-- .../proposal/active_voting_list.spec.mjs | 79 +++++++++++++++++++ .../proposal/get_locked_gwx_amount.spec.mjs | 2 +- 3 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 test/components/proposal/active_voting_list.spec.mjs diff --git a/ride/proposal.ride b/ride/proposal.ride index 56d8058a7..dcd8f44c3 100644 --- a/ride/proposal.ride +++ b/ride/proposal.ride @@ -102,6 +102,32 @@ func mustManager(i: Invocation) = { i.isManager() || "permission denied".throw() } +func updateActiveVotingListActions() = { + func map(acc: (List[String], Boolean), proposalIndexStr: String) = { + let (activeVotingListNew, shouldBeUpdated) = acc + let proposalIndex = proposalIndexStr.parseInt().value() + let votingInfo = this.getString(keyProposalInfo(proposalIndex)) + .valueOrErrorMessage(wrapErr("invalid proposal index in active voting list")) + let votingInfoArray = votingInfo.split(SEP) + let votingEndHeight = votingInfoArray[idxProposalInfoEnd].parseInt().value() + let votingIsOver = lastBlock.height > votingEndHeight + + if (votingIsOver) then { + (activeVotingListNew, true) + } else { + (activeVotingListNew :+ proposalIndexStr, shouldBeUpdated) + } + } + + let activeVotingList = getActiveVotingList() + let (activeVotingListNew, shouldBeUpdated) = FOLD<30>(activeVotingList, ([], false), map) + + if (shouldBeUpdated) then [ + StringEntry(keyActiveVotingList(), activeVotingListNew.makeString(SEP)) + ] else [] +} + +# TODO: add active voting list size limit @Callable(i) func startNewVote(name: String, description: String, expirationHeight: Int, quorumNumber: Int) = { strict checks = [ @@ -128,7 +154,6 @@ func startNewVote(name: String, description: String, expirationHeight: Int, quor ] } -# TODO: refresh active voting list @Callable(i) func voteFor(proposalIndex: Int, choice: Boolean) = { let EMPTY = "EMPTY" @@ -190,10 +215,9 @@ func voteFor(proposalIndex: Int, choice: Boolean) = { [ action1, action2 - ] + ] ++ updateActiveVotingListActions() } -# TODO: refresh active voting list @Callable(i) func deleteVote(proposalIndex: Int) = { let EMPTY = "EMPTY" @@ -233,10 +257,9 @@ func deleteVote(proposalIndex: Int) = { [ action, DeleteEntry(keyUserVoteOnProposal(proposalIndex, i.caller.toString())) - ] + ] ++ updateActiveVotingListActions() } -# TODO: refresh active voting list @Callable(i) func changeVote(proposalIndex: Int, choice: Boolean) = { let EMPTY = "EMPTY" @@ -301,7 +324,7 @@ func changeVote(proposalIndex: Int, choice: Boolean) = { gwxNumber.toString() ) ) - ] + ] ++ updateActiveVotingListActions() } @Callable(i) diff --git a/test/components/proposal/active_voting_list.spec.mjs b/test/components/proposal/active_voting_list.spec.mjs new file mode 100644 index 000000000..aff42b384 --- /dev/null +++ b/test/components/proposal/active_voting_list.spec.mjs @@ -0,0 +1,79 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { data } from '@waves/waves-transactions'; +import { + api, broadcastAndWait, chainId, waitForHeight, +} from '../../utils/api.mjs'; +import { proposal } from './contract/proposal.mjs'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +describe(`${process.pid}: proposal: active voting list`, () => { + let startHeight; + it('list should be updated after vote creation', async function () { + const keyActiveVotingList = '%s__activeVotingList'; + const duration = 60; + const quorumNumber = 1e8; + [{ height: startHeight }] = await Promise.all([ + proposal.startNewVote({ + dApp: this.accounts.proposal.address, + caller: this.accounts.proposal.seed, + name: 'test1', + description: 'test1', + duration, + quorumNumber, + }), + proposal.startNewVote({ + dApp: this.accounts.proposal.address, + caller: this.accounts.proposal.seed, + name: 'test2', + description: 'test2', + duration: 1, + quorumNumber, + }), + proposal.startNewVote({ + dApp: this.accounts.proposal.address, + caller: this.accounts.proposal.seed, + name: 'test3', + description: 'test3', + duration, + quorumNumber, + }), + ]); + + expect( + await api.addresses.fetchDataKey( + this.accounts.proposal.address, + keyActiveVotingList, + ).then(({ value }) => value), + ).to.equal('2__1__0'); + }); + + it('list should be updated when voting is over', async function () { + await waitForHeight(startHeight + 2); + const vote1 = 1e8; + await broadcastAndWait(data({ + data: [ + { + key: `%s%s__gwxAmountTotal__${this.accounts.user0.address}`, + value: vote1, + }, + ], + chainId, + }, this.accounts.gwxReward.seed)); + + const { stateChanges } = await proposal.voteFor({ + dApp: this.accounts.proposal.address, + caller: this.accounts.user0.seed, + proposalIndex: 0, + choice: true, + }); + + expect(stateChanges.data).to.deep.include({ + key: '%s__activeVotingList', + type: 'string', + value: '2__0', + }); + }); +}); diff --git a/test/components/proposal/get_locked_gwx_amount.spec.mjs b/test/components/proposal/get_locked_gwx_amount.spec.mjs index 631475066..065999cc2 100644 --- a/test/components/proposal/get_locked_gwx_amount.spec.mjs +++ b/test/components/proposal/get_locked_gwx_amount.spec.mjs @@ -7,7 +7,7 @@ import { proposal } from './contract/proposal.mjs'; chai.use(chaiAsPromised); const { expect } = chai; -describe(`${process.pid}: proposal`, () => { +describe(`${process.pid}: proposal: locked gwx amount`, () => { it('get locked gwx amount should work', async function () { const duration = 60; const quorumNumber = 1e8; From 0711badd4f9d1f69c3b55922f9d2af35c80458cf Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 28 Jul 2023 18:09:49 +0500 Subject: [PATCH 24/86] remove locked gwx amount from proposal --- ride/proposal.ride | 78 +---------------- .../proposal/active_voting_list.spec.mjs | 75 ++-------------- .../proposal/get_locked_gwx_amount.spec.mjs | 86 ------------------- 3 files changed, 13 insertions(+), 226 deletions(-) delete mode 100644 test/components/proposal/get_locked_gwx_amount.spec.mjs diff --git a/ride/proposal.ride b/ride/proposal.ride index dcd8f44c3..165c840a5 100644 --- a/ride/proposal.ride +++ b/ride/proposal.ride @@ -23,12 +23,6 @@ func keyUserVoteOnProposal(number: Int, user: String) = { ["%s%d%s", "userVoteOnProposal", number.toString(), user].makeString(SEP) } -func keyActiveVotingList() = ["%s", "activeVotingList"].makeString(SEP) -func getActiveVotingList() = match this.getString(keyActiveVotingList()) { - case s: String => s.split(SEP) - case _: Unit => [] -} - func keyManagerPublicKey() = "%s__managerPublicKey" func keyManagerVaultAddress() = "%s__managerVaultAddress" @@ -102,39 +96,12 @@ func mustManager(i: Invocation) = { i.isManager() || "permission denied".throw() } -func updateActiveVotingListActions() = { - func map(acc: (List[String], Boolean), proposalIndexStr: String) = { - let (activeVotingListNew, shouldBeUpdated) = acc - let proposalIndex = proposalIndexStr.parseInt().value() - let votingInfo = this.getString(keyProposalInfo(proposalIndex)) - .valueOrErrorMessage(wrapErr("invalid proposal index in active voting list")) - let votingInfoArray = votingInfo.split(SEP) - let votingEndHeight = votingInfoArray[idxProposalInfoEnd].parseInt().value() - let votingIsOver = lastBlock.height > votingEndHeight - - if (votingIsOver) then { - (activeVotingListNew, true) - } else { - (activeVotingListNew :+ proposalIndexStr, shouldBeUpdated) - } - } - - let activeVotingList = getActiveVotingList() - let (activeVotingListNew, shouldBeUpdated) = FOLD<30>(activeVotingList, ([], false), map) - - if (shouldBeUpdated) then [ - StringEntry(keyActiveVotingList(), activeVotingListNew.makeString(SEP)) - ] else [] -} - -# TODO: add active voting list size limit @Callable(i) func startNewVote(name: String, description: String, expirationHeight: Int, quorumNumber: Int) = { strict checks = [ i.mustManager() ] let theIndex = getCurrentIndex() - let newActiveVotingList = theIndex.toString() :: getActiveVotingList() [ IntegerEntry(keyCurrentIndex(), theIndex + 1), @@ -149,8 +116,7 @@ func startNewVote(name: String, description: String, expirationHeight: Int, quor "0" ) ), - StringEntry(keyProposalDescription(theIndex), description), - StringEntry(keyActiveVotingList(), newActiveVotingList.makeString(SEP)) + StringEntry(keyProposalDescription(theIndex), description) ] } @@ -215,7 +181,7 @@ func voteFor(proposalIndex: Int, choice: Boolean) = { [ action1, action2 - ] ++ updateActiveVotingListActions() + ] } @Callable(i) @@ -257,7 +223,7 @@ func deleteVote(proposalIndex: Int) = { [ action, DeleteEntry(keyUserVoteOnProposal(proposalIndex, i.caller.toString())) - ] ++ updateActiveVotingListActions() + ] } @Callable(i) @@ -324,7 +290,7 @@ func changeVote(proposalIndex: Int, choice: Boolean) = { gwxNumber.toString() ) ) - ] ++ updateActiveVotingListActions() + ] } @Callable(i) @@ -343,42 +309,6 @@ func getResultREADONLY(proposalIndex: Int) = { ([], [positiveVotes, negativeVotes, quorumNumber]) } -@Callable(i) -func getLockedGwxAmount(userAddressStr: String) = { - let userAddress = addressFromString(userAddressStr) - .valueOrErrorMessage(wrapErr("invalid address")) - let activeVotingList = getActiveVotingList() - func map(acc: List[Int], proposalIndexStr: String) = { - let proposalIndex = proposalIndexStr.parseInt().value() - let votingInfo = this.getString(keyProposalInfo(proposalIndex)) - .valueOrErrorMessage(wrapErr("invalid proposal index in active voting list")) - let votingInfoArray = votingInfo.split(SEP) - let votingEndHeight = votingInfoArray[idxProposalInfoEnd].parseInt().value() - let votingIsOver = lastBlock.height > votingEndHeight - let userVoteInfoOption = this.getString( - keyUserVoteOnProposal(proposalIndex, userAddressStr) - ) - let userVoteGwxAmount = match userVoteInfoOption { - case userVoteInfo: String => { - let userVoteInfoArray = userVoteInfo.split(SEP) - userVoteInfoArray[idxUserVoteOnProposalGwxNumber].parseInt().value() - } - case _: Unit => 0 - } - let activeVoteValue = if (votingIsOver) then { - 0 - } else { - userVoteGwxAmount - } - - activeVoteValue :: acc - } - let userVotes = FOLD<30>(activeVotingList, [], map) - let maxUserVote = max(userVotes) - - (nil, maxUserVote) -} - @Verifier(tx) func verify() = { let targetPublicKey = match managerPublicKeyOrUnit() { diff --git a/test/components/proposal/active_voting_list.spec.mjs b/test/components/proposal/active_voting_list.spec.mjs index aff42b384..b179e7bea 100644 --- a/test/components/proposal/active_voting_list.spec.mjs +++ b/test/components/proposal/active_voting_list.spec.mjs @@ -1,79 +1,22 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import { data } from '@waves/waves-transactions'; -import { - api, broadcastAndWait, chainId, waitForHeight, -} from '../../utils/api.mjs'; import { proposal } from './contract/proposal.mjs'; chai.use(chaiAsPromised); const { expect } = chai; -describe(`${process.pid}: proposal: active voting list`, () => { - let startHeight; - it('list should be updated after vote creation', async function () { - const keyActiveVotingList = '%s__activeVotingList'; +describe(`${process.pid}: proposal: start new vote`, () => { + it('new vote should be created', async function () { const duration = 60; const quorumNumber = 1e8; - [{ height: startHeight }] = await Promise.all([ - proposal.startNewVote({ - dApp: this.accounts.proposal.address, - caller: this.accounts.proposal.seed, - name: 'test1', - description: 'test1', - duration, - quorumNumber, - }), - proposal.startNewVote({ - dApp: this.accounts.proposal.address, - caller: this.accounts.proposal.seed, - name: 'test2', - description: 'test2', - duration: 1, - quorumNumber, - }), - proposal.startNewVote({ - dApp: this.accounts.proposal.address, - caller: this.accounts.proposal.seed, - name: 'test3', - description: 'test3', - duration, - quorumNumber, - }), - ]); - expect( - await api.addresses.fetchDataKey( - this.accounts.proposal.address, - keyActiveVotingList, - ).then(({ value }) => value), - ).to.equal('2__1__0'); - }); - - it('list should be updated when voting is over', async function () { - await waitForHeight(startHeight + 2); - const vote1 = 1e8; - await broadcastAndWait(data({ - data: [ - { - key: `%s%s__gwxAmountTotal__${this.accounts.user0.address}`, - value: vote1, - }, - ], - chainId, - }, this.accounts.gwxReward.seed)); - - const { stateChanges } = await proposal.voteFor({ + return expect(proposal.startNewVote({ dApp: this.accounts.proposal.address, - caller: this.accounts.user0.seed, - proposalIndex: 0, - choice: true, - }); - - expect(stateChanges.data).to.deep.include({ - key: '%s__activeVotingList', - type: 'string', - value: '2__0', - }); + caller: this.accounts.proposal.seed, + name: 'test1', + description: 'test1', + duration, + quorumNumber, + })).to.be.fulfilled; }); }); diff --git a/test/components/proposal/get_locked_gwx_amount.spec.mjs b/test/components/proposal/get_locked_gwx_amount.spec.mjs deleted file mode 100644 index 065999cc2..000000000 --- a/test/components/proposal/get_locked_gwx_amount.spec.mjs +++ /dev/null @@ -1,86 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import { data } from '@waves/waves-transactions'; -import { api, broadcastAndWait, chainId } from '../../utils/api.mjs'; -import { proposal } from './contract/proposal.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -describe(`${process.pid}: proposal: locked gwx amount`, () => { - it('get locked gwx amount should work', async function () { - const duration = 60; - const quorumNumber = 1e8; - await Promise.all([ - proposal.startNewVote({ - dApp: this.accounts.proposal.address, - caller: this.accounts.proposal.seed, - name: 'test1', - description: 'test1', - duration, - quorumNumber, - }), - proposal.startNewVote({ - dApp: this.accounts.proposal.address, - caller: this.accounts.proposal.seed, - name: 'test2', - description: 'test2', - duration, - quorumNumber, - }), - proposal.startNewVote({ - dApp: this.accounts.proposal.address, - caller: this.accounts.proposal.seed, - name: 'test3', - description: 'test3', - duration, - quorumNumber, - }), - ]); - - const vote1 = 1e8; - await broadcastAndWait(data({ - data: [ - { - key: `%s%s__gwxAmountTotal__${this.accounts.user0.address}`, - value: vote1, - }, - ], - chainId, - }, this.accounts.gwxReward.seed)); - - await proposal.voteFor({ - dApp: this.accounts.proposal.address, - caller: this.accounts.user0.seed, - proposalIndex: 0, - choice: true, - }); - - const vote2 = 2e8; - await broadcastAndWait(data({ - data: [ - { - key: `%s%s__gwxAmountTotal__${this.accounts.user0.address}`, - value: vote2, - }, - ], - chainId, - }, this.accounts.gwxReward.seed)); - - await proposal.voteFor({ - dApp: this.accounts.proposal.address, - caller: this.accounts.user0.seed, - proposalIndex: 2, - choice: true, - }); - - const expr = `getLockedGwxAmount("${this.accounts.user0.address}")`; - const response = await api.utils.fetchEvaluate( - this.accounts.proposal.address, - expr, - ); - const result = response.result.value._2.value; - - expect(result).to.equal(Math.max(vote1, vote2)); - }); -}); From e49ca51a7d1d08c5793fd409cd29f147f73b13c6 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 28 Jul 2023 18:12:56 +0500 Subject: [PATCH 25/86] fix getLockedGwxAmount draft do not take into account proposal and voting_verification --- ride/boosting_v2.ride | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 31c8a5dd5..5347f1925 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -267,16 +267,12 @@ func getGwxAmountTotal() = { func getLockedGwxAmount(userAddress: Address) = { # TODO: get locked amount for userAddress (invoke or state) - let lockedProposal = 0 let lockedVotingEmissionRate = 0 let lockedVotingEmission = 0 - let lockedVotingVerified = 0 let locked = max([ - lockedProposal, lockedVotingEmissionRate, - lockedVotingEmission, - lockedVotingVerified + lockedVotingEmission ]) locked From 7771ce685ff201d5129f06c7310412bb10133f70 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 1 Aug 2023 14:19:18 +0500 Subject: [PATCH 26/86] voting emission rate getLockedGwxAmount --- ride/voting_emission_rate.ride | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ride/voting_emission_rate.ride b/ride/voting_emission_rate.ride index b50e6adc1..f040dc38c 100644 --- a/ride/voting_emission_rate.ride +++ b/ride/voting_emission_rate.ride @@ -279,6 +279,17 @@ func finalize() = { } } +@Callable(i) +func getLockedGwxAmount(userAddressStr: String) = { + let startHeight = keyStartHeight.getIntegerValue() + let gwxAmount = match this.getString(keyVote(userAddressStr, startHeight)) { + case _: Unit => 0 + case s: String => s.split(separator)[1].parseInt().valueOrElse(0) + } + + (nil, gwxAmount) +} + @Verifier(tx) func verify() = { let targetPublicKey = match managerPublicKeyOrUnit() { From d29bd48a30736e21875d87653ad93125531f02a6 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 1 Aug 2023 14:24:24 +0500 Subject: [PATCH 27/86] voting emission getLockedGwxAmount --- ride/voting_emission.ride | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ride/voting_emission.ride b/ride/voting_emission.ride index 093627381..b087a40f3 100644 --- a/ride/voting_emission.ride +++ b/ride/voting_emission.ride @@ -768,6 +768,19 @@ func isFinalizationInProgress() = { (nil, finalizationInProgress) } +@Callable(i) +func getLockedGwxAmount(userAddressStr: String) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let epoch = this.getInteger(keyCurrentEpoch).valueOrElse(0) + let gwxAmount = match this.getInteger(keyUsed(userAddress, epoch)) { + case _: Unit => 0 + case n: Int => n + } + + (nil, gwxAmount) +} + @Verifier(tx) func verify() = { let targetPublicKey = match managerPublicKeyOrUnit() { From 7605e2b3527a636e5017339ca46b03c976c213be Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 1 Aug 2023 14:29:58 +0500 Subject: [PATCH 28/86] boosting_v2 getLockedGwxAmount fix --- ride/boosting_v2.ride | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index 5347f1925..ea05eaa3a 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -251,6 +251,8 @@ let lpStakingPoolsContract = ["%s", "lpStakingPoolsContract"].makeString(SEP) let keyVotingEmissionContract = ["%s", "votingEmissionContract"].makeString(SEP) let votingEmissionContract = factoryContract.getStringValue(keyVotingEmissionContract).addressFromStringValue() +# in voting emission contract storage +let keyVotingEmissionRateContract = ["%s", "votingEmissionRateContract"].makeString(SEP) let boostCoeff = emissionContract.invoke("getBoostCoeffREADONLY", [], []).exactAs[Int] @@ -266,9 +268,15 @@ func getGwxAmountTotal() = { } func getLockedGwxAmount(userAddress: Address) = { - # TODO: get locked amount for userAddress (invoke or state) - let lockedVotingEmissionRate = 0 - let lockedVotingEmission = 0 + let functionName = "getLockedGwxAmount" + let votingEmissionRateContract = { + match votingEmissionContract.getString(keyVotingEmissionRateContract) { + case _: Unit => unit + case s: String => addressFromString(s) + } + }.valueOrErrorMessage(wrapErr("invalid voting emission rate address")) + let lockedVotingEmissionRate = votingEmissionContract.invoke(functionName, [userAddress.toString()], []).exactAs[Int] + let lockedVotingEmission = votingEmissionRateContract.invoke(functionName, [userAddress.toString()], []).exactAs[Int] let locked = max([ lockedVotingEmissionRate, From 76dfa2cd3f7086cc47f50b92c08c39b76635fc5f Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 2 Aug 2023 20:02:41 +0500 Subject: [PATCH 29/86] migration script --- migrations/wxdefi-435-release/.gitignore | 3 + migrations/wxdefi-435-release/index.mjs | 174 +++++++++++++++++++++ migrations/wxdefi-435-release/package.json | 15 ++ ride/boosting_v2.ride | 4 +- 4 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 migrations/wxdefi-435-release/.gitignore create mode 100644 migrations/wxdefi-435-release/index.mjs create mode 100644 migrations/wxdefi-435-release/package.json diff --git a/migrations/wxdefi-435-release/.gitignore b/migrations/wxdefi-435-release/.gitignore new file mode 100644 index 000000000..92d0d10e0 --- /dev/null +++ b/migrations/wxdefi-435-release/.gitignore @@ -0,0 +1,3 @@ +/txs +.env* +pnpm-lock.yaml diff --git a/migrations/wxdefi-435-release/index.mjs b/migrations/wxdefi-435-release/index.mjs new file mode 100644 index 000000000..c7d11f10a --- /dev/null +++ b/migrations/wxdefi-435-release/index.mjs @@ -0,0 +1,174 @@ +import { create } from '@waves/node-api-js'; +import { BigNumber } from '@waves/bignumber'; +import { + data, + reissue, + invokeScript, + transfer, +} from '@waves/waves-transactions'; +import fs from 'fs/promises'; +import path from 'path'; +import * as dotenv from 'dotenv'; +import { address } from '@waves/ts-lib-crypto'; + +dotenv.config(); + +const separator = '__'; +const scriptedSenderFee = 4e5; + +const { + NODE_URL, + CHAIN_ID, + TXS_PATH, + GWX_REWARD_PUBLIC_KEY, + BOOSTING_PUBLIC_KEY, + WX_ASSET_ID, + MIN_LOCK_AMOUNT, + MIN_LOCK_DURATION, + MAX_LOCK_DURATION, +} = process.env; +const api = create(NODE_URL); + +const boostingAddress = address( + { publicKey: BOOSTING_PUBLIC_KEY }, + CHAIN_ID +); + +const gwxRewardAddress = address( + { publicKey: GWX_REWARD_PUBLIC_KEY }, + CHAIN_ID +); + +const keyLockParamsRecordOld = (userAddress) => + `%s%s__lock__${userAddress}`; + +const keyLockParamsRecord = (userAddress, txId) => + `%s%s__lock__${userAddress}__${txId}`; + +const lockParamsRecord = ({ + amount, + start, + duration, + timestamp, + gwxAmount, + wxClaimed, +}) => [ + '%d%d%d%d%d%d%d', + amount.toString(), + start.toString(), + duration.toString(), + timestamp.toString(), + gwxAmount.toString(), + wxClaimed.toString(), +].join(separator); + +const lockParamsData = await api.addresses.data(boostingAddress, { + matches: encodeURIComponent( + `%s%s__lock__.+` + ), +}); + +const actions = []; +let gwxAmountTotal = 0; +for (const { key, value } of lockParamsData) { + const [ + /* meta */, + /* 'lock' */, + userAddress + ] = key.split(separator); + + const [ + /* meta */, + /* userNum */, + amount, + start, + duration, + /* paramK */, + /* paramB */, + timestamp, // last update timestamp + gwxAmount + ] = value.split(separator) + + if (amount <= 0) continue; + + gwxAmountTotal += parseInt(gwxAmount); + actions.push({ + key: keyLockParamsRecord(userAddress, 'legacy'), + type: 'string', + value: lockParamsRecord({ amount, start, duration, timestamp, gwxAmount, wxClaimed: 0 }), + }); +} +const chunkSize = 100; +const actionsChunks = Array.from( + { length: Math.ceil(actions.length / chunkSize) }, + () => [] +); +for (const i in actions) { + const chunkIndex = Math.floor(i / chunkSize); + actionsChunks[chunkIndex].push(actions[i]); +} +const dataTxs = actionsChunks.map((changes) => + data({ + data: changes, + chainId: CHAIN_ID, + senderPublicKey: BOOSTING_PUBLIC_KEY, + additionalFee: scriptedSenderFee, + }) +); + +const txs = []; + +// restart the script after boosting lock/unlock freeze? +txs.push({ + name: 'restart the script', + tx: {}, +}); + +txs.push({ + name: 'boosting data', + tx: data({ + data: [ + { + key: '%s%s__gwx__total', + type: 'integer', + value: gwxAmountTotal, + }, + { + key: '%s__config', + type: 'string', + value: [ + '%s%d%d%d%s', + WX_ASSET_ID, + MIN_LOCK_AMOUNT.toString(), + MIN_LOCK_DURATION.toString(), + MAX_LOCK_DURATION.toString(), + gwxRewardAddress, + ].join(separator), + } + ], + senderPublicKey: BOOSTING_PUBLIC_KEY, + additionalFee: scriptedSenderFee, + chainId: CHAIN_ID, + }), +}); + +for (const tx of dataTxs) { + const name = 'lock params'; + txs.push({ tx, name }); +} + +await fs.mkdir(TXS_PATH, { recursive: true }); +const files = await fs.readdir(TXS_PATH); +await Promise.all( + files.map(async (name) => { + return fs.unlink(path.join(TXS_PATH, name)); + }) +); +await Promise.all( + txs.map(async ({ tx, name }, idx) => { + await fs.writeFile( + path.join(TXS_PATH, `${[idx, name.replace(/\s/g, '_')].join('_')}.json`), + JSON.stringify(tx, null, 2) + ); + }) +); diff --git a/migrations/wxdefi-435-release/package.json b/migrations/wxdefi-435-release/package.json new file mode 100644 index 000000000..cd82cf997 --- /dev/null +++ b/migrations/wxdefi-435-release/package.json @@ -0,0 +1,15 @@ +{ + "name": "2022_12_27_lp_staking_pools_release", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "dotenv": "^16.0.3" + } +} diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index ea05eaa3a..fe9cf1fe7 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -6,7 +6,7 @@ # * "%s%s__config__referralsContractAddress": String # * "%s__nextUserNum": Int # * "%s%s__config__factoryAddress": String -# * "%s__config": String ("%s%d%d%d________") +# * "%s__config": String ("%s%d%d%d%s__________") # * "%s__lpStakingPoolsContract": String let SEP = "__" @@ -103,7 +103,7 @@ let mathContract = cfgArray[IdxCfgMathContract].addressFromString().valueOrError func formatConfigS(assetId: String, minLockAmount: String, minLockDuration: String, maxLockDuration: String, mathContract: String) = { makeString([ - "%s%d%d%d", + "%s%d%d%d%s", assetId, # 1 minLockAmount, # 2 minLockDuration, # 3 From a6d8931da3a2ad195e2f5a62a9617dcc3d26cb69 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 3 Aug 2023 15:32:01 +0500 Subject: [PATCH 30/86] remove unused imports --- migrations/wxdefi-435-release/index.mjs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/migrations/wxdefi-435-release/index.mjs b/migrations/wxdefi-435-release/index.mjs index c7d11f10a..b32629340 100644 --- a/migrations/wxdefi-435-release/index.mjs +++ b/migrations/wxdefi-435-release/index.mjs @@ -1,11 +1,5 @@ import { create } from '@waves/node-api-js'; -import { BigNumber } from '@waves/bignumber'; -import { - data, - reissue, - invokeScript, - transfer, -} from '@waves/waves-transactions'; +import { data } from '@waves/waves-transactions'; import fs from 'fs/promises'; import path from 'path'; import * as dotenv from 'dotenv'; From 966fa24cebbe35840e7f48963902a3ba1ce6a96e Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 3 Aug 2023 18:46:46 +0500 Subject: [PATCH 31/86] remove usersListTraversal there is a staking.onModifyWeight call in factory.modifyWeight --- ride/staking.ride | 35 ----------------------------------- ride/voting_emission.ride | 37 ++++++++++++++++--------------------- 2 files changed, 16 insertions(+), 56 deletions(-) diff --git a/ride/staking.ride b/ride/staking.ride index 398c1f89c..e98fe7713 100644 --- a/ride/staking.ride +++ b/ride/staking.ride @@ -711,41 +711,6 @@ func stakedTotalREADONLY(lpAssetIdStr: String) = { ([], stakedTotal) } -@Callable(i) -func usersListTraversal(lpAssetId: String) = { - strict checkCaller = i.caller.bytes.toBase58String() == this.getString(keyVotingEmissionContract()).valueOrElse("") || i.mustManager() - let listName = lpAssetId.getUsersListName() - let userOrUnit = lpAssetId.keyNextUser().getString() - let headOrUnit = listName.keyListHead().getString() - match userOrUnit { - case _: Unit => { - match headOrUnit { - case _: Unit => ([], false) - case head: String => ([ - StringEntry(lpAssetId.keyNextUser(), head) - ], true) - } - } - case userAddress: String => { - let claimedByUserMinReward = this.getBigIntFromStringOrZero(lpAssetId.keyClaimedByUserMinReward(userAddress)) - let poolAddress = factoryContract.getStringByAddressOrFail(lpAssetId.keyFactoryLpAssetToPoolContractAddress()) - let wxToClaimUserNew = refreshINTEGRALS(lpAssetId, userAddress, poolAddress, 0)._1 - let availableToClaim = wxToClaimUserNew - claimedByUserMinReward - let throwIfNothingToClaim = true - strict r = if (availableToClaim > zeroBigInt) then this.invoke("claimWxINTERNAL", [lpAssetId, userAddress, throwIfNothingToClaim], []) else unit - let nextUserOrUnit = listName.keyListNext(userAddress).getString() - match nextUserOrUnit { - case _: Unit => ([ - DeleteEntry(lpAssetId.keyNextUser()) - ], false) - case nextUser: String => ([ - StringEntry(lpAssetId.keyNextUser(), nextUser) - ], true) - } - } - } -} - @Callable(i) func onModifyWeight(lpAssetIdStr: String, poolAddressStr: String) = { if (i.caller != factoryContract) then throwErr("permissions denied") else diff --git a/ride/voting_emission.ride b/ride/voting_emission.ride index b087a40f3..5cc9708a2 100644 --- a/ride/voting_emission.ride +++ b/ride/voting_emission.ride @@ -505,28 +505,23 @@ func processPoolINTERNAL(poolStr: String, force: Boolean) = { let stakingContract = this.getStrOrFail(keyStakingContract).addressFromStringValue() let lpAssetId = getLpAssetByPoolAssets(amountAssetId, priceAssetId) - strict r = stakingContract.invoke("usersListTraversal", [lpAssetId], []).exactAs[Boolean] - if (r) then { - ([], true) - } else { - let wxEmission = pool.checkWxEmissionPoolLabel() - let totalVotes = this.getInteger(targetEpoch.keyTotalVotes()).valueOrElse(0) - let votingResult = this.getInteger(pool.keyVotingResult(targetEpoch)).valueOrElse(0) - let share = if (totalVotes == 0 || !wxEmission) then 0 else fraction(votingResult, poolWeightMult, totalVotes) - strict modifyWeightInv = factoryContract.invoke("modifyWeight", [lpAssetId, share], []) - let poolsListActions = if (wxEmission || force) then [] else { - [ - DeleteEntry(pool.keyInList()) - ] ++ poolsListName.deleteNodeActions(poolStr) - } - - ( - [ - IntegerEntry(pool.keyPoolShare(targetEpoch), share) - ] ++ poolsListActions, - false - ) + let wxEmission = pool.checkWxEmissionPoolLabel() + let totalVotes = this.getInteger(targetEpoch.keyTotalVotes()).valueOrElse(0) + let votingResult = this.getInteger(pool.keyVotingResult(targetEpoch)).valueOrElse(0) + let share = if (totalVotes == 0 || !wxEmission) then 0 else fraction(votingResult, poolWeightMult, totalVotes) + strict modifyWeightInv = factoryContract.invoke("modifyWeight", [lpAssetId, share], []) + let poolsListActions = if (wxEmission || force) then [] else { + [ + DeleteEntry(pool.keyInList()) + ] ++ poolsListName.deleteNodeActions(poolStr) } + + ( + [ + IntegerEntry(pool.keyPoolShare(targetEpoch), share) + ] ++ poolsListActions, + false + ) } # Может вызвать любой From 3d542a217af4d9d4325798b81dde0720570d9df6 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 3 Aug 2023 20:07:05 +0500 Subject: [PATCH 32/86] gwx reward update draft --- ride/boosting_v2.ride | 9 +++++ ride/gwx_reward.ride | 92 +++++++++++++------------------------------ 2 files changed, 37 insertions(+), 64 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index fe9cf1fe7..fd6daf87e 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -754,6 +754,15 @@ func getUserGwxAmountAtHeightREADONLY(userAddressStr: String, targetHeight: Int) ([], gwxAmount) } +@Callable(i) +func getUserGwxAmount(userAddressStr: String) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let gwxAmount = userAddress.getUserGwxAmountTotal() + + ([], gwxAmount) +} + @Callable(i) func getGwxTotalREADONLY() = { ([], getGwxAmountTotal()) diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index 179dd9da3..c927b46ee 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -9,6 +9,8 @@ # * "%s%s__config__referralsContractAddress": String # * "%s__latestPeriod": Integer +# TODO: seems like we can use keyGwxRewardEmissionStartHeight for integral start height + let SEP = "__" let SCALE = 1000 let MULT8 = 1_0000_0000 @@ -491,17 +493,33 @@ func deposit() = { @Callable(i) func claimReward() = { let cfgArray = readConfigArrayOrFail() - let address = i.caller.toString() - let (amount, actions) = commonClaimReward(address) - strict checkAmount = amount > 0 || "Nothing to claim".throw() - # remove if unused - let amountFromEmission = 0 - let claimedReferral = referralsContractAddressOrFail.invoke("claim", [referralProgramName], []).exactAs[Int] + let userAddress = i.caller + let userAddressStr = userAddress.toString() + let (amountLegacy, actionsLegacy) = commonClaimReward(userAddressStr) + # TODO: calculate using integrals + let amount = 0 + strict checkAmount = amount + amountLegacy > 0 || "nothing to claim".throw() + + let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddressStr], []).exactAs[Int] + let referrer = referralsContractAddressOrFail.getString(userAddressStr.keyReferrer()) + strict activeReferralInv = if (referrer == unit) then unit else { + referralsContractAddressOrFail.invoke("updateReferralActivity", [referralProgramName, userAddress, userGwxAmount >= referralMinGWxAmount], []) + } + strict referralInv = if (referrer == unit || userGwxAmount < referralMinGWxAmount) then unit else { + let referrerReward = amount.fraction(referrerRewardPermille, SCALE) + let referralReward = amount.fraction(referralRewardPermille, SCALE) + referralsContractAddressOrFail.invoke("incUnclaimed", [referralProgramName, userAddress, referrerReward, referralReward], []) + } + + strict claimedReferral = referralsContractAddressOrFail.invoke("claim", [referralProgramName], []).exactAs[Int] let totalAmount = amount + claimedReferral - ([ - ScriptTransfer(i.caller, totalAmount, cfgArray[IdxCfgAssetId].fromBase58String()), - HistoryEntry("claim", address, amount, i) - ] ++ actions, [totalAmount, amountFromEmission]) + ( + [ + ScriptTransfer(i.caller, amount, cfgArray[IdxCfgAssetId].fromBase58String()), + HistoryEntry("claim", userAddressStr, totalAmount, i) + ] ++ actionsLegacy, + totalAmount + ) } # returns total claimable reward by user address @@ -557,60 +575,6 @@ func latestPeriodEmissionRewardsREADONLY(address: String) = { ([], [getNumberByKey(keyAuxEmissionRewardForPeriod(period))]) } -# LP Math - -# D invariant calculation iteratively for 2 tokens -# -# A * sum(x_i) * n^n + D = A * D * n^n + D^(n+1) / (n^n * prod(x_i)) -# -# Converging solution: -# D[j+1] = (A * n^n * sum(x_i) - D[j]^(n+1) / (n^n prod(x_i))) / (A * n^n - 1) -@Callable(i) -func calcD( - x1BigIntStr: String, - x2BigIntStr: String, - ampBigIntStr: String, - aPrecisionBigIntStr: String, - targetPrecisionBigIntStr: String -) = { - let nCoins = 2.toBigInt() - let aPrecision = aPrecisionBigIntStr.parseBigIntValue() - let targetPrecision = targetPrecisionBigIntStr.parseBigIntValue() - let x1 = x1BigIntStr.parseBigIntValue() - let x2 = x2BigIntStr.parseBigIntValue() - let amp = ampBigIntStr.parseBigIntValue() * aPrecision - let s = x1 + x2 - if (s == zeroBigInt) then { - ([], zeroBigInt.toString()) - } else { - let ann = amp * nCoins - let arr = [0, 1, 2, 3, 4, 5, 6] - func calc(acc: (BigInt, BigInt|Unit, Int|Unit), cur: Int) = { - let (d, dPrev, found) = acc - if (found != unit) then acc else { - # dp0 = d - # dp1 = dp0 * d / (x1 * nCoins) - # dp2 = dp1 * d / (x2 * nCoins) = (dp0 * d / (x1 * nCoins)) * d / (x2 * nCoins) = d^3 / (x1 * x2 * nCoins^2) - let dp = d * d * d / (x1 * x2 * nCoins * nCoins) - let dNext = (ann * s / aPrecision + dp * nCoins) * d / ((ann - aPrecision) * d / aPrecision + (nCoins + 1.toBigInt()) * dp) - let dDiff = absBigInt(dNext - d.value()) - if (dDiff <= targetPrecision) then { - (dNext, d, cur) - } else { - (dNext, d, unit) - } - } - } - let (dNext, dPrev, found) = FOLD<7>(arr, (s, unit, unit), calc) - if (found != unit) then { - ([], dNext.toString()) - } else { - let dDiff = { dNext - dPrev.value() }.absBigInt() - { "D calculation error, dDiff = " + dDiff.toString() }.throw() - } - } -} - @Callable(i) func tradeReward(userAddresses: List[String], rewards: List[Int]) = { let argsComparison = userAddresses.size() == rewards.size() From 5301693141976a45efd430c629d4f698a82dac30 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:37:39 +0500 Subject: [PATCH 33/86] refreshRewardPerGwxIntegral draft --- ride/gwx_reward.ride | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index c927b46ee..1fc2989d0 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -17,6 +17,8 @@ let MULT8 = 1_0000_0000 let zeroBigInt = 0.toBigInt() let processingStageTotal = 0 let processingStageShares = 1 +let MULT18 = 1_000_000_000_000_000_000 +let MULT18BI = MULT18.toBigInt() let wavesString = "WAVES" @@ -258,6 +260,43 @@ func mustManager(i: Invocation) = { } } +func keyRewardPerGwxIntegral() = ["%s", "rewardPerGwxIntegral"].makeString(SEP) + +# call when reward or total gwx amount are changed +func refreshRewardPerGwxIntegral() = { + # rewardPerGwx = dh * emissionPerBlock / totalGwxAmount + rewardPerGwxPrevious + let rewardPerGwxIntegralPrevious = { + match this.getString(keyRewardPerGwxIntegral()) { + case s: String => s.parseBigInt() + case _: Unit => unit + } + }.valueOrElse(zeroBigInt) + let rewardPerGwxIntegralLastHeight = this.getInteger(keyGwxRewardEmissionStartHeight()) + .valueOrErrorMessage(wrapErr("invalid " + keyGwxRewardEmissionStartHeight())) + let emissionRate = emissionContract.getInteger(keyRatePerBlockCurrent()) + .valueOrErrorMessage(wrapErr("invalid " + keyRatePerBlockCurrent())) + let gwxHoldersRewardCurrent = emissionContract.getInteger(keyGwxHoldersRewardCurrent()) + .valueOrElse(0) + let gwxAmountTotal = boostingContractOrFail().invoke("getGwxTotalREADONLY", [], []).exactAs[Int] + let dh = { height - rewardPerGwxIntegralLastHeight }.toBigInt() + let rewardPerGwxIntegral = rewardPerGwxIntegralPrevious + fraction( + dh, + emissionRate.toBigInt() * gwxHoldersRewardCurrent.toBigInt() * MULT18BI, + gwxAmountTotal.toBigInt() + ) + + [ + IntegerEntry(keyGwxRewardEmissionStartHeight(), height), + StringEntry(keyRewardPerGwxIntegral(), rewardPerGwxIntegral.toString()) + ] +} + +func refreshUserReward() = { + # userReward = userGwxAmount * rewardPerGwxIntegral + userRewardPrevious - userRewardClaimed + + unit +} + # user's weight = k * height + b, scaled by 10^8 func calcUserWeight(boostingContractAddress: Address, heightForPeriod: Int, period: Int, userIndex: Int) = { let kLast = keyLastProcessedPeriodOfUser(userIndex) From 0ad2b856bb69ca166e4291d19528bce90f7d513f Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:46:04 +0500 Subject: [PATCH 34/86] refreshUserReward draft --- ride/gwx_reward.ride | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index 1fc2989d0..43847c4ad 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -285,14 +285,27 @@ func refreshRewardPerGwxIntegral() = { gwxAmountTotal.toBigInt() ) - [ - IntegerEntry(keyGwxRewardEmissionStartHeight(), height), - StringEntry(keyRewardPerGwxIntegral(), rewardPerGwxIntegral.toString()) - ] + ( + [ + IntegerEntry(keyGwxRewardEmissionStartHeight(), height), + StringEntry(keyRewardPerGwxIntegral(), rewardPerGwxIntegral.toString()) + ], + rewardPerGwxIntegral + ) } -func refreshUserReward() = { - # userReward = userGwxAmount * rewardPerGwxIntegral + userRewardPrevious - userRewardClaimed +func keyRewardPerGwxIntegralUserLast(userAddress: Address) = ["%s%s", "rewardPerGwxIntegralUserLast", userAddress.toString()].makeString(SEP) + +# call when user total gwx amount is changed +func refreshUserReward(userAddress: Address) = { + let rewardPerGwxIntegralUserLast = { + match this.getString(keyRewardPerGwxIntegralUserLast(userAddress)) { + case s: String => s.parseBigInt() + case _: Unit => unit + } + }.valueOrElse(zeroBigInt) + + # userReward = userGwxAmount * (rewardPerGwxIntegral - rewardPerGwxIntegralUserLast) + userRewardPrevious - userRewardClaimed unit } From 5832b71b51f8b2c9f6990dd04c5b2c0ad76146af Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:44:52 +0500 Subject: [PATCH 35/86] voting emission remove deposit call --- ride/voting_emission.ride | 9 --------- 1 file changed, 9 deletions(-) diff --git a/ride/voting_emission.ride b/ride/voting_emission.ride index 5cc9708a2..b0798bf42 100644 --- a/ride/voting_emission.ride +++ b/ride/voting_emission.ride @@ -121,13 +121,6 @@ func checkWxEmissionPoolLabel(pool: (String, String)) = { factoryContract.invoke("checkWxEmissionPoolLabel", [amountAssetId, priceAssetId], []).exactAs[Boolean] } -func gwxRewardDeposit() = { - let factoryCfg = factoryContract.readFactoryCfgOrFail() - let gwxRewardsContract = factoryCfg.getGwxRewardAddressOrFail() - - gwxRewardsContract.invoke("deposit", [], []) -} - # Doubly linked list -> let poolsListName = "pools" func getVotesListName(pool: (String, String)) = { @@ -658,7 +651,6 @@ func finalizeHelper() = { IntegerEntry(keyStartHeightUi, startHeight) ] } - strict gwxRewardDepositInv = gwxRewardDeposit() (actions, true) } case nextPoolStr: String => { @@ -689,7 +681,6 @@ func finalizeHelper() = { DeleteEntry(keyNextPool) ] } - strict gwxRewardDepositInv = gwxRewardDeposit() (actions, true) } case nextPoolStr: String => { From 17783797c9014be056890e490eb2e37a4f3da64e Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:48:16 +0500 Subject: [PATCH 36/86] gwx reward refreshUserReward --- ride/gwx_reward.ride | 144 ++++++------- ride/gwx_reward_v2.ride | 451 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 515 insertions(+), 80 deletions(-) create mode 100644 ride/gwx_reward_v2.ride diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index 43847c4ad..179dd9da3 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -9,16 +9,12 @@ # * "%s%s__config__referralsContractAddress": String # * "%s__latestPeriod": Integer -# TODO: seems like we can use keyGwxRewardEmissionStartHeight for integral start height - let SEP = "__" let SCALE = 1000 let MULT8 = 1_0000_0000 let zeroBigInt = 0.toBigInt() let processingStageTotal = 0 let processingStageShares = 1 -let MULT18 = 1_000_000_000_000_000_000 -let MULT18BI = MULT18.toBigInt() let wavesString = "WAVES" @@ -260,56 +256,6 @@ func mustManager(i: Invocation) = { } } -func keyRewardPerGwxIntegral() = ["%s", "rewardPerGwxIntegral"].makeString(SEP) - -# call when reward or total gwx amount are changed -func refreshRewardPerGwxIntegral() = { - # rewardPerGwx = dh * emissionPerBlock / totalGwxAmount + rewardPerGwxPrevious - let rewardPerGwxIntegralPrevious = { - match this.getString(keyRewardPerGwxIntegral()) { - case s: String => s.parseBigInt() - case _: Unit => unit - } - }.valueOrElse(zeroBigInt) - let rewardPerGwxIntegralLastHeight = this.getInteger(keyGwxRewardEmissionStartHeight()) - .valueOrErrorMessage(wrapErr("invalid " + keyGwxRewardEmissionStartHeight())) - let emissionRate = emissionContract.getInteger(keyRatePerBlockCurrent()) - .valueOrErrorMessage(wrapErr("invalid " + keyRatePerBlockCurrent())) - let gwxHoldersRewardCurrent = emissionContract.getInteger(keyGwxHoldersRewardCurrent()) - .valueOrElse(0) - let gwxAmountTotal = boostingContractOrFail().invoke("getGwxTotalREADONLY", [], []).exactAs[Int] - let dh = { height - rewardPerGwxIntegralLastHeight }.toBigInt() - let rewardPerGwxIntegral = rewardPerGwxIntegralPrevious + fraction( - dh, - emissionRate.toBigInt() * gwxHoldersRewardCurrent.toBigInt() * MULT18BI, - gwxAmountTotal.toBigInt() - ) - - ( - [ - IntegerEntry(keyGwxRewardEmissionStartHeight(), height), - StringEntry(keyRewardPerGwxIntegral(), rewardPerGwxIntegral.toString()) - ], - rewardPerGwxIntegral - ) -} - -func keyRewardPerGwxIntegralUserLast(userAddress: Address) = ["%s%s", "rewardPerGwxIntegralUserLast", userAddress.toString()].makeString(SEP) - -# call when user total gwx amount is changed -func refreshUserReward(userAddress: Address) = { - let rewardPerGwxIntegralUserLast = { - match this.getString(keyRewardPerGwxIntegralUserLast(userAddress)) { - case s: String => s.parseBigInt() - case _: Unit => unit - } - }.valueOrElse(zeroBigInt) - - # userReward = userGwxAmount * (rewardPerGwxIntegral - rewardPerGwxIntegralUserLast) + userRewardPrevious - userRewardClaimed - - unit -} - # user's weight = k * height + b, scaled by 10^8 func calcUserWeight(boostingContractAddress: Address, heightForPeriod: Int, period: Int, userIndex: Int) = { let kLast = keyLastProcessedPeriodOfUser(userIndex) @@ -545,33 +491,17 @@ func deposit() = { @Callable(i) func claimReward() = { let cfgArray = readConfigArrayOrFail() - let userAddress = i.caller - let userAddressStr = userAddress.toString() - let (amountLegacy, actionsLegacy) = commonClaimReward(userAddressStr) - # TODO: calculate using integrals - let amount = 0 - strict checkAmount = amount + amountLegacy > 0 || "nothing to claim".throw() - - let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddressStr], []).exactAs[Int] - let referrer = referralsContractAddressOrFail.getString(userAddressStr.keyReferrer()) - strict activeReferralInv = if (referrer == unit) then unit else { - referralsContractAddressOrFail.invoke("updateReferralActivity", [referralProgramName, userAddress, userGwxAmount >= referralMinGWxAmount], []) - } - strict referralInv = if (referrer == unit || userGwxAmount < referralMinGWxAmount) then unit else { - let referrerReward = amount.fraction(referrerRewardPermille, SCALE) - let referralReward = amount.fraction(referralRewardPermille, SCALE) - referralsContractAddressOrFail.invoke("incUnclaimed", [referralProgramName, userAddress, referrerReward, referralReward], []) - } - - strict claimedReferral = referralsContractAddressOrFail.invoke("claim", [referralProgramName], []).exactAs[Int] + let address = i.caller.toString() + let (amount, actions) = commonClaimReward(address) + strict checkAmount = amount > 0 || "Nothing to claim".throw() + # remove if unused + let amountFromEmission = 0 + let claimedReferral = referralsContractAddressOrFail.invoke("claim", [referralProgramName], []).exactAs[Int] let totalAmount = amount + claimedReferral - ( - [ - ScriptTransfer(i.caller, amount, cfgArray[IdxCfgAssetId].fromBase58String()), - HistoryEntry("claim", userAddressStr, totalAmount, i) - ] ++ actionsLegacy, - totalAmount - ) + ([ + ScriptTransfer(i.caller, totalAmount, cfgArray[IdxCfgAssetId].fromBase58String()), + HistoryEntry("claim", address, amount, i) + ] ++ actions, [totalAmount, amountFromEmission]) } # returns total claimable reward by user address @@ -627,6 +557,60 @@ func latestPeriodEmissionRewardsREADONLY(address: String) = { ([], [getNumberByKey(keyAuxEmissionRewardForPeriod(period))]) } +# LP Math + +# D invariant calculation iteratively for 2 tokens +# +# A * sum(x_i) * n^n + D = A * D * n^n + D^(n+1) / (n^n * prod(x_i)) +# +# Converging solution: +# D[j+1] = (A * n^n * sum(x_i) - D[j]^(n+1) / (n^n prod(x_i))) / (A * n^n - 1) +@Callable(i) +func calcD( + x1BigIntStr: String, + x2BigIntStr: String, + ampBigIntStr: String, + aPrecisionBigIntStr: String, + targetPrecisionBigIntStr: String +) = { + let nCoins = 2.toBigInt() + let aPrecision = aPrecisionBigIntStr.parseBigIntValue() + let targetPrecision = targetPrecisionBigIntStr.parseBigIntValue() + let x1 = x1BigIntStr.parseBigIntValue() + let x2 = x2BigIntStr.parseBigIntValue() + let amp = ampBigIntStr.parseBigIntValue() * aPrecision + let s = x1 + x2 + if (s == zeroBigInt) then { + ([], zeroBigInt.toString()) + } else { + let ann = amp * nCoins + let arr = [0, 1, 2, 3, 4, 5, 6] + func calc(acc: (BigInt, BigInt|Unit, Int|Unit), cur: Int) = { + let (d, dPrev, found) = acc + if (found != unit) then acc else { + # dp0 = d + # dp1 = dp0 * d / (x1 * nCoins) + # dp2 = dp1 * d / (x2 * nCoins) = (dp0 * d / (x1 * nCoins)) * d / (x2 * nCoins) = d^3 / (x1 * x2 * nCoins^2) + let dp = d * d * d / (x1 * x2 * nCoins * nCoins) + let dNext = (ann * s / aPrecision + dp * nCoins) * d / ((ann - aPrecision) * d / aPrecision + (nCoins + 1.toBigInt()) * dp) + let dDiff = absBigInt(dNext - d.value()) + if (dDiff <= targetPrecision) then { + (dNext, d, cur) + } else { + (dNext, d, unit) + } + } + } + let (dNext, dPrev, found) = FOLD<7>(arr, (s, unit, unit), calc) + if (found != unit) then { + ([], dNext.toString()) + } else { + let dDiff = { dNext - dPrev.value() }.absBigInt() + { "D calculation error, dDiff = " + dDiff.toString() }.throw() + } + } +} + @Callable(i) func tradeReward(userAddresses: List[String], rewards: List[Int]) = { let argsComparison = userAddresses.size() == rewards.size() diff --git a/ride/gwx_reward_v2.ride b/ride/gwx_reward_v2.ride new file mode 100644 index 000000000..52c86df15 --- /dev/null +++ b/ride/gwx_reward_v2.ride @@ -0,0 +1,451 @@ +{-# STDLIB_VERSION 6 #-} +{-# CONTENT_TYPE DAPP #-} +{-# SCRIPT_TYPE ACCOUNT #-} + +# Required state entries: +# * "%s__config": String ("%s%s%s______") +# * "%s%s__config__factoryAddress": String +# * "%s%s__config__emissionAddress": String +# * "%s%s__config__referralsContractAddress": String +# * "%s__latestPeriod": Integer + +# TODO: seems like we can use keyGwxRewardEmissionStartHeight for integral start height + +let SEP = "__" +let SCALE = 1000 +let MULT8 = 1_0000_0000 +let zeroBigInt = 0.toBigInt() +let processingStageTotal = 0 +let processingStageShares = 1 +let MULT18 = 1_000_000_000_000_000_000 +let MULT18BI = MULT18.toBigInt() + +let wavesString = "WAVES" + +func getNumberByKey(key: String) = this.getInteger(key).valueOrElse(0) +func getNumberOrFail(key: String) = this.getInteger(key).valueOrErrorMessage("mandatory this." + key + " is not defined") +func getStringByKey(key: String) = this.getString(key).valueOrElse("") +func getStringOrFail(key: String) = this.getString(key).valueOrErrorMessage("mandatory this." + key + " is not defined") + +func parseAssetId(input: String) = { + if (input == wavesString) then unit else input.fromBase58String() +} + +func wrapErr(msg: String) = ["gwx_reward.ride:", msg].makeString(" ") +func throwErr(msg: String) = msg.wrapErr().throw() + +func abs(val: Int) = if (val < 0) then -val else val +func absBigInt(val: BigInt) = if (val < zeroBigInt) then -val else val + +let keyMaxDepth = "%s__maxDepth" +let maxDepthDefault = 30 +let maxDepth = this.getInteger(keyMaxDepth).valueOrElse(maxDepthDefault) + +# FACTORY API +# own factory address key +func keyFactoryAddress() = "%s%s__config__factoryAddress" + +# GLOBAL VARIABLES +let factoryAddressStr = getStringOrFail(keyFactoryAddress()) +let factoryContract = factoryAddressStr.addressFromStringValue() + +# EMISSION API +# own emission address key +func keyEmissionAddress() = "%s%s__config__emissionAddress" + +func keyVotingEmissionContract() = ["%s", "votingEmissionContract"].makeString(SEP) +let votingEmissionContract = factoryContract.getStringValue(keyVotingEmissionContract()).addressFromStringValue() + +# Boosting +func keyNumToUserMapping(num: Int) = ["%s%s%s", "mapping", "num2user", num.toString()].makeString(SEP) + +# Referrals +let keyReferralProgramName = ["%s%s", "referral", "programName"].makeString(SEP) +let referralProgramNameDefault = "wxlock" +let referralProgramName = this.getString(keyReferralProgramName).valueOrElse(referralProgramNameDefault) + +let keyReferralMinGWxAmount = ["%s%s", "referral", "minGWxAmount"].makeString(SEP) +let referralMinGWxAmountDefault = 500 * MULT8 +let referralMinGWxAmount = this.getInteger(keyReferralMinGWxAmount).valueOrElse(referralMinGWxAmountDefault) + +let keyReferrerRewardPermille = ["%s%s", "referral", "referrerRewardPermille"].makeString(SEP) +# 50‰ = 5% +let referrerRewardPermilleDefault = 50 +let referrerRewardPermille = this.getInteger(keyReferrerRewardPermille).valueOrElse(referrerRewardPermilleDefault) + +let keyReferralRewardPermille = ["%s%s", "referral", "referralRewardPermille"].makeString(SEP) +# 50‰ = 5% +let referralRewardPermilleDefault = 50 +let referralRewardPermille = this.getInteger(keyReferralRewardPermille).valueOrElse(referralRewardPermilleDefault) + +func keyReferrer(referralAddress: String) = ["%s%s%s", "referrer", referralProgramName, referralAddress].makeString(SEP) + +func keyUnclaimedReferral( + programName: String, + claimerAddress: String +) = makeString(["%s%s%s", "unclaimedReferral", programName, claimerAddress], SEP) + +# GLOBAL VARIABLES +# CONSTRUCTOR IS NOT FAILED BECAUSE GLOBAL VARIABLES ARE NOT USED +let emissionAddressStr = getStringOrFail(keyEmissionAddress()) +let emissionContract = emissionAddressStr.addressFromStringValue() + + +# *********************** +# Config +# *********************** +# index 0 corresponds %s%s%s metadata +let IdxCfgAssetId = 1 +let IdxCfgPacemakerAddress = 2 +let IdxCfgBoostingContract = 3 +let IdxCfgMaxDepth = 4 + +func keyConfig() = "%s__config" + +func getEmissionAddress() = this.getString( + keyEmissionAddress() +).valueOrErrorMessage( + "mandatory this." + keyEmissionAddress() + " is not defined" +).addressFromStringValue() + + +let emissionAddress = getEmissionAddress() +let wxAssetIdStr = emissionAddress.getString(keyConfig()).value().split(SEP)[1] +let wxAssetId = wxAssetIdStr.fromBase58String() + +func readConfigArrayOrFail() = getStringOrFail(keyConfig()).split(SEP) + +func formatConfig(wxAssetIdStr: String, matcherPacemakerAddressStr: String, boostingContractAddressStr: String, maxDepth: Int) = { + makeString([ + "%s%s%s%d", + wxAssetIdStr, # 1 + matcherPacemakerAddressStr, # 2 + boostingContractAddressStr, # 3 + maxDepth.toString() # 4 + ], SEP) +} + +func boostingContractOrFail() = { + let cfgArray = readConfigArrayOrFail() + cfgArray[IdxCfgBoostingContract].addressFromString().valueOrErrorMessage("boosting contract address is not defined") +} + +# *********************** +# KEYS +# *********************** + +func keyGwxRewardEmissionStartHeight() = "%s%s__gwxRewardEmissionPart__startHeight" + +# boosting contract state key, increments every lock() of unique user +func keyUsersCount() = "%s__nextUserNum" + +# emission contract key +func keyRatePerBlockCurrent() = "%s%s__ratePerBlock__current" +func keyGwxHoldersRewardCurrent() = "%s%s__gwxHoldersReward__current" +func keyGwxHoldersRewardNext() = "%s%s__gwxHoldersReward__next" + +# factory contract key +func keyPoolWeightVirtual() = "%s%s__poolWeight__GWXvirtualPOOL" + +func keyUserUnclaimed(userIndex: Int) = + ["%s%d", "userUnclaimed", userIndex.toString()].makeString(SEP) + +func keyReferralsContractAddress() = ["%s%s", "config", "referralsContractAddress"].makeString(SEP) +let referralsContractAddressOrFail = keyReferralsContractAddress().getStringOrFail().addressFromStringValue() + +func keyTradingRewardHistory(user: String, i: Invocation) = { + makeString(["%s%s%s%s", "tradingReward", "history", user, i.transactionId.toBase58String()], SEP) +} + +func keyTradingReward(userAddress: String) = { + makeString(["%s%s", "tradingReward", userAddress], SEP) +} + +func keyMaxRecipients() = ["%s", "maxRecipients"].makeString(SEP) + +func HistoryEntry(type: String, user: String, amount: Int, i: Invocation) = { + let historyKEY = makeString(["%s%s%s%s__history", type, user, i.transactionId.toBase58String()], SEP) + let historyDATA = makeString([ + "%d%d%d%d%d%d", + lastBlock.height.toString(), + lastBlock.timestamp.toString(), + amount.toString()], + SEP) + StringEntry(historyKEY, historyDATA) +} + +func keyManagerPublicKey() = "%s__managerPublicKey" +func keyManagerVaultAddress() = "%s__managerVaultAddress" + +func getManagerVaultAddressOrThis() = { + match keyManagerVaultAddress().getString() { + case s:String => s.addressFromStringValue() + case _=> this + } +} + +func managerPublicKeyOrUnit() = { + let managerVaultAddress = getManagerVaultAddressOrThis() + match managerVaultAddress.getString(keyManagerPublicKey()) { + case s: String => s.fromBase58String() + case _: Unit => unit + } +} + +func mustManager(i: Invocation) = { + let pd = "Permission denied".throw() + + match managerPublicKeyOrUnit() { + case pk: ByteVector => i.callerPublicKey == pk || pd + case _: Unit => i.caller == this || pd + } +} + +func getUserIndexByAddress(boostingContractAddressStr: String, userAddress: String) = { + let key = makeString(["%s%s%s", "mapping", "user2num", userAddress], SEP) + parseIntValue(getString(Address(boostingContractAddressStr.fromBase58String()), key) + .valueOrErrorMessage("User address " + userAddress + " is not found in boosting contract data, key=" + key)) +} + +func commonClaimReward(userAddress: String) = { + let cfgArray = readConfigArrayOrFail() + let userIdx = getUserIndexByAddress(cfgArray[IdxCfgBoostingContract], userAddress) # will throw if no such user + let userUnclaimedOption = userIdx.keyUserUnclaimed().getInteger() + match userUnclaimedOption { + case _: Unit => (0, []) + case u: Int => (u, [ + IntegerEntry(userIdx.keyUserUnclaimed(), 0) + ]) + } +} + +func getTradingReward(userAddress: String) = { + this.getInteger(userAddress.keyTradingReward()).valueOrElse(0) +} + +func keyRewardPerGwxIntegral() = ["%s", "rewardPerGwxIntegral"].makeString(SEP) + +# call when reward or total gwx amount are changed +func refreshRewardPerGwxIntegral() = { + # rewardPerGwx = dh * emissionPerBlock / totalGwxAmount + rewardPerGwxPrevious + let rewardPerGwxIntegralPrevious = { + match this.getString(keyRewardPerGwxIntegral()) { + case s: String => s.parseBigInt() + case _: Unit => unit + } + }.valueOrElse(zeroBigInt) + let rewardPerGwxIntegralLastHeight = this.getInteger(keyGwxRewardEmissionStartHeight()) + .valueOrErrorMessage(wrapErr("invalid " + keyGwxRewardEmissionStartHeight())) + let emissionRate = emissionContract.getInteger(keyRatePerBlockCurrent()) + .valueOrErrorMessage(wrapErr("invalid " + keyRatePerBlockCurrent())) + let gwxHoldersRewardCurrent = emissionContract.getInteger(keyGwxHoldersRewardCurrent()) + .valueOrElse(0) + let gwxAmountTotal = boostingContractOrFail().invoke("getGwxTotalREADONLY", [], []).exactAs[Int] + let dh = { height - rewardPerGwxIntegralLastHeight }.toBigInt() + let rewardPerGwxIntegral = rewardPerGwxIntegralPrevious + fraction( + dh, + emissionRate.toBigInt() * gwxHoldersRewardCurrent.toBigInt() * MULT18BI, + gwxAmountTotal.toBigInt() + ) + + ( + [ + IntegerEntry(keyGwxRewardEmissionStartHeight(), height), + StringEntry(keyRewardPerGwxIntegral(), rewardPerGwxIntegral.toString()) + ], + rewardPerGwxIntegral + ) +} + +func keyRewardPerGwxIntegralUserLast(userAddress: Address) = ["%s%s", "rewardPerGwxIntegralUserLast", userAddress.toString()].makeString(SEP) + +# call when user total gwx amount is changed +func refreshUserReward(userAddress: Address) = { + let rewardPerGwxIntegralUserLast = { + match this.getString(keyRewardPerGwxIntegralUserLast(userAddress)) { + case s: String => s.parseBigInt() + case _: Unit => unit + } + }.valueOrElse(zeroBigInt) + + let userIdxOrFail = getUserIndexByAddress( + boostingContractOrFail().toString(), + userAddress.toString() + ) + let userUnclaimed = userIdxOrFail.keyUserUnclaimed().getInteger().valueOrElse(0) + + let (rewardPerGwxIntegralActions, rewardPerGwxIntegral) = refreshRewardPerGwxIntegral() + + # userReward = userGwxAmount * (rewardPerGwxIntegral - rewardPerGwxIntegralUserLast) + userRewardPrevious - userRewardClaimed + let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddress.toString()], []).exactAs[Int] + let userReward = fraction(userGwxAmount.toBigInt(), rewardPerGwxIntegral - rewardPerGwxIntegralUserLast, MULT18BI).toInt() + userUnclaimed + + ( + [ + StringEntry( + keyRewardPerGwxIntegralUserLast(userAddress), + rewardPerGwxIntegral.toString() + ) + ] ++ rewardPerGwxIntegralActions, + userReward + ) +} + +@Callable(i) +func tradeRewardInternal( + paymentAmountLeftOver: Int, + userAddresses: List[String], + rewards: List[Int], + currentIter: Int +) = { + if (currentIter == userAddresses.size()) then ([]) else + + strict checks = [ + i.caller == this || "Permission denied".throwErr(), + paymentAmountLeftOver >= rewards[currentIter] || "insufficient payment assetId".throwErr() + ] + + strict tradeRewardInternal = this.invoke( + "tradeRewardInternal", + [ + paymentAmountLeftOver - rewards[currentIter], + userAddresses, + rewards, + currentIter+1 + ], + [] + ) + + let tradingRewardHistoryKey = keyTradingRewardHistory( + userAddresses[currentIter], + i + ) + + let userAddress = addressFromStringValue(userAddresses[currentIter]) + + ([ + IntegerEntry(tradingRewardHistoryKey, rewards[currentIter]), + IntegerEntry(userAddresses[currentIter].keyTradingReward(), rewards[currentIter]) + ], tradeRewardInternal) +} + +@Callable(i) +func updateReferralActivity(userAddress: String, gWxAmountStart: Int) = { + let referrer = referralsContractAddressOrFail.getString(userAddress.keyReferrer()) + strict activeReferralInv = if (referrer == unit) then unit else { + referralsContractAddressOrFail.invoke("updateReferralActivity", [referralProgramName, userAddress, gWxAmountStart >= referralMinGWxAmount], []) + } + + (nil, unit) +} + +@Callable(i) +func processPendingPeriodsAndUsers() = { + (nil, throwErr("deprecated")) +} + +# Send all WX earned to caller +# called by user +@Callable(i) +func claimReward() = { + let cfgArray = readConfigArrayOrFail() + let userAddress = i.caller + let userAddressStr = userAddress.toString() + let (amountLegacy, actionsLegacy) = commonClaimReward(userAddressStr) + # TODO: calculate using integrals + let amount = 0 + strict checkAmount = amount + amountLegacy > 0 || "nothing to claim".throw() + + let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddressStr], []).exactAs[Int] + let referrer = referralsContractAddressOrFail.getString(userAddressStr.keyReferrer()) + strict activeReferralInv = if (referrer == unit) then unit else { + referralsContractAddressOrFail.invoke("updateReferralActivity", [referralProgramName, userAddress, userGwxAmount >= referralMinGWxAmount], []) + } + strict referralInv = if (referrer == unit || userGwxAmount < referralMinGWxAmount) then unit else { + let referrerReward = amount.fraction(referrerRewardPermille, SCALE) + let referralReward = amount.fraction(referralRewardPermille, SCALE) + referralsContractAddressOrFail.invoke("incUnclaimed", [referralProgramName, userAddress, referrerReward, referralReward], []) + } + + strict claimedReferral = referralsContractAddressOrFail.invoke("claim", [referralProgramName], []).exactAs[Int] + let totalAmount = amount + claimedReferral + ( + [ + ScriptTransfer(i.caller, amount, cfgArray[IdxCfgAssetId].fromBase58String()), + HistoryEntry("claim", userAddressStr, totalAmount, i) + ] ++ actionsLegacy, + totalAmount + ) +} + +# returns total claimable reward by user address +@Callable(i) +func claimRewardREADONLY(address: String) = { + let (amount, actions) = commonClaimReward(address) + let referralUnclaimed = referralsContractAddressOrFail.getInteger(keyUnclaimedReferral(referralProgramName, address)).valueOrElse(0) + let totalAmount = amount + referralUnclaimed + + ([], totalAmount) +} + +# save starting height of reward from emission 5% +@Callable(i) +func onEmissionForGwxStart() = { + if (i.caller != factoryContract) then throw("permissions denied") else + [IntegerEntry(keyGwxRewardEmissionStartHeight(), height)] +} + +@Callable(i) +func tradeReward(userAddresses: List[String], rewards: List[Int]) = { + let argsComparison = userAddresses.size() == rewards.size() + let maxRecipients = keyMaxRecipients().getInteger().valueOrElse(0) + let payment = i.payments[0] + let paymentAssetId = payment.assetId + let paymentAmount = payment.amount + + strict checks = [ + userAddresses.size() <= maxRecipients || "Too many recipients".throwErr(), + argsComparison || "Arguments size mismatch".throwErr(), + paymentAssetId == wxAssetId || "Wrong asset payment".throwErr() + ] + + strict tradeRewardInternal = this.invoke( + "tradeRewardInternal", + [ + paymentAmount, + userAddresses, + rewards, + 0 + ], + [] + ) + + (nil, tradeRewardInternal) +} + +@Callable(i) +func claimTradingReward() = { + let userAddress = i.caller + let userAddressString = userAddress.toString() + let reward = userAddressString.getTradingReward() + if (reward > 0) then { + ([ + ScriptTransfer(userAddress, reward, wxAssetId), + IntegerEntry(keyTradingReward(userAddressString), 0) + ], reward) + } else "nothing to claim".throwErr() +} + +@Callable(i) +func claimTradingRewardREADONLY(userAddress: String) = { + (nil, userAddress.getTradingReward()) +} + +@Verifier(tx) +func verify() = { + let targetPublicKey = match managerPublicKeyOrUnit() { + case pk: ByteVector => pk + case _: Unit => tx.senderPublicKey + } + sigVerify(tx.bodyBytes, tx.proofs[0], targetPublicKey) +} From b1b596c6242fed6c3eca577e665c4219fc573566 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:49:41 +0500 Subject: [PATCH 37/86] refreshUserReward in lock/unlock + increase precision --- ride/boosting_v2.ride | 21 ++++++++++++++------- ride/gwx_reward_v2.ride | 13 ++++++++++--- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride index fd6daf87e..4122a4ac2 100644 --- a/ride/boosting_v2.ride +++ b/ride/boosting_v2.ride @@ -14,6 +14,9 @@ let SCALE8 = 8 let MULT8 = 100000000 let POOLWEIGHTMULT = MULT8 let contractFilename = "boosting.ride" +let SCALE18 = 18 +let MULT18 = 1_000_000_000_000_000_000 +let MULT18BI = MULT18.toBigInt() # 24 * 60 let blocksInDay = 1440 @@ -606,6 +609,8 @@ func lockActions(i: Invocation, durationMonths: Int) = { let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) + strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes], []) + let arr = if (userIsExisting) then [] else [ IntegerEntry(nextUserNumKEY, userNum + 1), StringEntry(keyUser2NumMapping(userAddressStr), userNumStr), @@ -685,18 +690,18 @@ func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { let t = (height - lockStart) / blocksInDay let exponent = fraction( - t, - 8 * blocksInDay * MULT8, - lockDuration + t.toBigInt(), + { 8 * blocksInDay }.toBigInt() * MULT18BI, + lockDuration.toBigInt() ) let wxWithdrawable = if (height > lockEnd) then { userAmount - wxClaimed } else { fraction( - userAmount, - MULT8 - pow(5, 1, exponent, SCALE8, SCALE8, DOWN), - MULT8 - ) + userAmount.toBigInt(), + MULT18BI - pow(5.toBigInt(), 1, exponent, SCALE18, SCALE18, DOWN), + MULT18BI + ).toInt() } if (amount > wxWithdrawable) @@ -719,6 +724,8 @@ func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { let gwxAmountTotal = getGwxAmountTotal() let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) + strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes], []) + LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + amount) ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, gwxBurned, i) diff --git a/ride/gwx_reward_v2.ride b/ride/gwx_reward_v2.ride index 52c86df15..6334bbcd2 100644 --- a/ride/gwx_reward_v2.ride +++ b/ride/gwx_reward_v2.ride @@ -226,7 +226,7 @@ func getTradingReward(userAddress: String) = { func keyRewardPerGwxIntegral() = ["%s", "rewardPerGwxIntegral"].makeString(SEP) # call when reward or total gwx amount are changed -func refreshRewardPerGwxIntegral() = { +func _refreshRewardPerGwxIntegral() = { # rewardPerGwx = dh * emissionPerBlock / totalGwxAmount + rewardPerGwxPrevious let rewardPerGwxIntegralPrevious = { match this.getString(keyRewardPerGwxIntegral()) { @@ -260,7 +260,7 @@ func refreshRewardPerGwxIntegral() = { func keyRewardPerGwxIntegralUserLast(userAddress: Address) = ["%s%s", "rewardPerGwxIntegralUserLast", userAddress.toString()].makeString(SEP) # call when user total gwx amount is changed -func refreshUserReward(userAddress: Address) = { +func _refreshUserReward(userAddress: Address) = { let rewardPerGwxIntegralUserLast = { match this.getString(keyRewardPerGwxIntegralUserLast(userAddress)) { case s: String => s.parseBigInt() @@ -274,7 +274,7 @@ func refreshUserReward(userAddress: Address) = { ) let userUnclaimed = userIdxOrFail.keyUserUnclaimed().getInteger().valueOrElse(0) - let (rewardPerGwxIntegralActions, rewardPerGwxIntegral) = refreshRewardPerGwxIntegral() + let (rewardPerGwxIntegralActions, rewardPerGwxIntegral) = _refreshRewardPerGwxIntegral() # userReward = userGwxAmount * (rewardPerGwxIntegral - rewardPerGwxIntegralUserLast) + userRewardPrevious - userRewardClaimed let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddress.toString()], []).exactAs[Int] @@ -291,6 +291,13 @@ func refreshUserReward(userAddress: Address) = { ) } +@Callable(i) +func refreshUserReward(userAddressBytes: ByteVector) = { + strict checkCaller = i.caller == boostingContractOrFail() || throwErr("permission denied") + + _refreshUserReward(Address(userAddressBytes)) +} + @Callable(i) func tradeRewardInternal( paymentAmountLeftOver: Int, From 2f3a4cd8fbd67055429925ea8adb85b3de591153 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:53:51 +0500 Subject: [PATCH 38/86] fix claimReward --- ride/gwx_reward_v2.ride | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ride/gwx_reward_v2.ride b/ride/gwx_reward_v2.ride index 6334bbcd2..d666127df 100644 --- a/ride/gwx_reward_v2.ride +++ b/ride/gwx_reward_v2.ride @@ -358,10 +358,8 @@ func claimReward() = { let cfgArray = readConfigArrayOrFail() let userAddress = i.caller let userAddressStr = userAddress.toString() - let (amountLegacy, actionsLegacy) = commonClaimReward(userAddressStr) - # TODO: calculate using integrals - let amount = 0 - strict checkAmount = amount + amountLegacy > 0 || "nothing to claim".throw() + let (amount, actions) = commonClaimReward(userAddressStr) + strict checkAmount = amount > 0 || "nothing to claim".throw() let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddressStr], []).exactAs[Int] let referrer = referralsContractAddressOrFail.getString(userAddressStr.keyReferrer()) @@ -380,7 +378,7 @@ func claimReward() = { [ ScriptTransfer(i.caller, amount, cfgArray[IdxCfgAssetId].fromBase58String()), HistoryEntry("claim", userAddressStr, totalAmount, i) - ] ++ actionsLegacy, + ] ++ actions, totalAmount ) } From 37a10e53809dce39167ffc52502f8297b91b5916 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:42:45 +0500 Subject: [PATCH 39/86] rename + info in migration --- migrations/wxdefi-435-release/index.mjs | 1 + ride/boosting.ride | 593 ++++++++--------- ride/boosting_v2.ride | 830 ------------------------ ride/gwx_reward.ride | 419 +++--------- ride/gwx_reward_v2.ride | 456 ------------- 5 files changed, 371 insertions(+), 1928 deletions(-) delete mode 100644 ride/boosting_v2.ride delete mode 100644 ride/gwx_reward_v2.ride diff --git a/migrations/wxdefi-435-release/index.mjs b/migrations/wxdefi-435-release/index.mjs index b32629340..bcbb1e6f7 100644 --- a/migrations/wxdefi-435-release/index.mjs +++ b/migrations/wxdefi-435-release/index.mjs @@ -166,3 +166,4 @@ await Promise.all( ); }) ); +console.log(`Done. ${txs.length} txs created.`) diff --git a/ride/boosting.ride b/ride/boosting.ride index ad440637f..4122a4ac2 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -6,45 +6,39 @@ # * "%s%s__config__referralsContractAddress": String # * "%s__nextUserNum": Int # * "%s%s__config__factoryAddress": String -# * "%s__config": String ("%s%d%d%d________") +# * "%s__config": String ("%s%d%d%d%s__________") # * "%s__lpStakingPoolsContract": String let SEP = "__" -let SCALE8 = 8 -let MULT8 = 100000000 +let SCALE8 = 8 +let MULT8 = 100000000 let POOLWEIGHTMULT = MULT8 +let contractFilename = "boosting.ride" +let SCALE18 = 18 +let MULT18 = 1_000_000_000_000_000_000 +let MULT18BI = MULT18.toBigInt() -func wrapErr(msg: String) = ["boosting.ride:", msg].makeString(" ") +# 24 * 60 +let blocksInDay = 1440 +# 365 * 24 * 60 / 12 +let blocksInMonth = 43800 + +func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") func throwErr(msg: String) = msg.wrapErr().throw() -# getStringOrFail -func strf(address: Address, key: String) = address.getString(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) -# getIntOrZero -func ioz(address: Address, key: String) = address.getInteger(key).valueOrElse(0) -# getIntOrDefault -func iod(address: Address, key: String, defaultVal: Int) = address.getInteger(key).valueOrElse(defaultVal) -# getIntOrFail -func iof(address: Address, key: String) = address.getInteger(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) -func abs(val: Int) = if (val < 0) then -val else val +func getStringOrFail(address: Address, key: String) = address.getString(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) +func getIntOrZero(address: Address, key: String) = address.getInteger(key).valueOrElse(0) +func getIntOrDefault(address: Address, key: String, defaultVal: Int) = address.getInteger(key).valueOrElse(defaultVal) +func getIntOrFail(address: Address, key: String) = address.getInteger(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) -# asAnyList -func aal(val: Any) = { - match val { - case valAnyLyst: List[Any] => valAnyLyst - case _ => throwErr("fail to cast into List[Any]") - } -} +func abs(val: Int) = if (val < 0) then -val else val -# asInt -func ai(val: Any) = { - match val { - case valInt: Int => valInt - case _ => throwErr("fail to cast into Int") - } +func ensurePositive(v: Int, m: String|Unit) = { + if (v >= 0) then v else throwErr(m.valueOrElse("value") + " should be positive") } -func keyReferralsContractAddress() = ["%s%s", "config", "referralsContractAddress"].makeString(SEP) -let referralsContractAddressOrFail = this.strf(keyReferralsContractAddress()).addressFromStringValue() +func keyReferralsContractAddress() = ["%s%s", "config", "referralsContractAddress"].makeString(SEP) +let referralsContractAddressOrFail = this.getStringOrFail(keyReferralsContractAddress()).addressFromStringValue() let keyReferralProgramName = ["%s%s", "referral", "programName"].makeString(SEP) let referralProgramNameDefault = "wxlock" @@ -52,33 +46,31 @@ let referralProgramName = this.getString(keyReferralProgramName).valueOrElse(ref # FACTORY API # own factory address key -func keyFactoryAddress() = "%s%s__config__factoryAddress" - -let IdxFactoryCfgStakingDapp = 1 -let IdxFactoryCfgBoostingDapp = 2 -let IdxFactoryCfgIdoDapp = 3 -let IdxFactoryCfgTeamDapp = 4 -let IdxFactoryCfgEmissionDapp = 5 -let IdxFactoryCfgRestDapp = 6 -let IdxFactoryCfgSlippageDapp = 7 -let IdxFactoryCfgDaoDapp = 8 -let IdxFactoryCfgMarketingDapp = 9 -let IdxFactoryCfgGwxRewardDapp = 10 -let IdxFactoryCfgBirdsDapp = 11 - -func keyFactoryCfg() = "%s__factoryConfig" -func keyFactoryLpList() = "%s__lpTokensList" # not used anymore +func keyFactoryAddress() = "%s%s__config__factoryAddress" + +let IdxFactoryCfgStakingDapp = 1 +let IdxFactoryCfgBoostingDapp = 2 +let IdxFactoryCfgIdoDapp = 3 +let IdxFactoryCfgTeamDapp = 4 +let IdxFactoryCfgEmissionDapp = 5 +let IdxFactoryCfgRestDapp = 6 +let IdxFactoryCfgSlippageDapp = 7 +let IdxFactoryCfgDaoDapp = 8 +let IdxFactoryCfgMarketingDapp = 9 +let IdxFactoryCfgGwxRewardDapp = 10 +let IdxFactoryCfgBirdsDapp = 11 + +func keyFactoryCfg() = "%s__factoryConfig" func keyFactoryLpAssetToPoolContractAddress(lpAssetStr: String) = makeString(["%s%s%s", lpAssetStr, "mappings__lpAsset2PoolContract"], SEP) func keyFactoryPoolWeight(contractAddress: String) = { ["%s%s", "poolWeight", contractAddress].makeString(SEP) } func keyFactoryPoolWeightHistory(poolAddress: String, num: Int) = {"%s%s__poolWeight__" + poolAddress + "__" + num.toString()} -func readFactoryAddressOrFail() = this.strf(keyFactoryAddress()).addressFromStringValue() -func readLpList() = readFactoryAddressOrFail().getString(keyFactoryLpList()).valueOrElse("").split(SEP) -func readFactoryCfgOrFail(factory: Address) = factory.strf(keyFactoryCfg()).split(SEP) +func readFactoryAddressOrFail() = this.getStringOrFail(keyFactoryAddress()).addressFromStringValue() +func readFactoryCfgOrFail(factory: Address) = factory.getStringOrFail(keyFactoryCfg()).split(SEP) func getBoostingAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgBoostingDapp].addressFromStringValue() func getEmissionAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgEmissionDapp].addressFromStringValue() -func getStakingAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgStakingDapp].addressFromStringValue() -func getGwxRewardAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgGwxRewardDapp].addressFromStringValue() +func getStakingAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgStakingDapp].addressFromStringValue() +func getGwxRewardAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgGwxRewardDapp].addressFromStringValue() func keyManagerPublicKey() = "%s__managerPublicKey" func keyManagerVaultAddress() = "%s__managerVaultAddress" @@ -93,39 +85,44 @@ func keyEmissionDurationInBlocks() = "%s%s__emission__duration" func keyEmissionEndBlock() = "%s%s__emission__endBlock" # GWX REWARD (MATH) API -func keyNextPeriod() = "%s__nextPeriod" +func keyNextPergetIntOrDefault() = "%s__nextPeriod" func keyGwxRewardEmissionStartHeight() = "%s%s__gwxRewardEmissionPart__startHeight" # OWN KEYS -let IdxCfgAssetId = 1 -let IdxCfgMinLockAmount = 2 -let IdxCfgMinLockDuration = 3 -let IdxCfgMaxLockDuration = 4 -let IdxCfgMathContract = 5 +let IdxCfgAssetId = 1 +let IdxCfgMinLockAmount = 2 +let IdxCfgMinLockDuration = 3 +let IdxCfgMaxLockDuration = 4 +let IdxCfgMathContract = 5 func keyConfig() = {"%s__config"} -func readConfigArrayOrFail() = this.strf(keyConfig()).split(SEP) -let mathContract = readConfigArrayOrFail()[IdxCfgMathContract].addressFromStringValue() +func readConfigArrayOrFail() = this.getStringOrFail(keyConfig()).split(SEP) +let cfgArray = readConfigArrayOrFail() +let assetId = cfgArray[IdxCfgAssetId].fromBase58String() +let minLockAmount = cfgArray[IdxCfgMinLockAmount].parseInt().valueOrErrorMessage(wrapErr("invalid min lock amount")) +let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseInt().valueOrErrorMessage(wrapErr("invalid min lock duration")) +let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseInt().valueOrErrorMessage(wrapErr("invalid max lock duration")) +let mathContract = cfgArray[IdxCfgMathContract].addressFromString().valueOrErrorMessage(wrapErr("invalid math contract address")) func formatConfigS(assetId: String, minLockAmount: String, minLockDuration: String, maxLockDuration: String, mathContract: String) = { makeString([ - "%s%d%d%d", - assetId, # 1 - minLockAmount, # 2 - minLockDuration, # 3 - maxLockDuration, # 4 - mathContract # 5 + "%s%d%d%d%s", + assetId, # 1 + minLockAmount, # 2 + minLockDuration, # 3 + maxLockDuration, # 4 + mathContract # 5 ], SEP) } func formatConfig(assetId: String, minLockAmount: Int, minLockDuration: Int, maxLockDuration: Int, mathContract: String) = { formatConfigS( - assetId, # 1 - minLockAmount.toString(), # 2 - minLockDuration.toString(), # 3 - maxLockDuration.toString(), # 4 - mathContract # 5 + assetId, # 1 + minLockAmount.toString(), # 2 + minLockDuration.toString(), # 3 + maxLockDuration.toString(), # 4 + mathContract # 5 ) } @@ -153,74 +150,55 @@ func mustManager(i: Invocation) = { } } -let IdxLockUserNum = 1 -let IdxLockAmount = 2 -let IdxLockStart = 3 -let IdxLockDuration = 4 -let IdxLockParamK = 5 -let IdxLockParamB = 6 +let IdxLockAmount = 1 +let IdxLockStart = 2 +let IdxLockDuration = 3 +let IdxLockLastUpdateTimestamp = 4 +let IdxLockGwxAmount = 5 +let IdxLockWxClaimed = 6 -func keyLockParamsRecord(userAddress: String) = makeString(["%s%s__lock", userAddress], SEP) -func readLockParamsRecordOrFail(userAddress: String) = this.strf(keyLockParamsRecord(userAddress)).split(SEP) +func keyLockParamsRecord(userAddress: Address, txId: ByteVector) = makeString(["%s%s%s__lock", userAddress.toString(), txId.toBase58String()], SEP) +func readLockParamsRecordOrFail(userAddress: Address, txId: ByteVector) = this.getStringOrFail(keyLockParamsRecord(userAddress, txId)).split(SEP) -func formatLockParamsRecordS(userNum: String, amount: String, start: String, duration: String, paramK: String, paramB: String, lastUpdTimestamp: String, gwxAmount: String) = { +func keyUserGwxAmountTotal(userAddress: Address) = makeString(["%s%s__gwxAmountTotal", userAddress.toString()], SEP) +func formatLockParamsRecord( + amount: Int, + start: Int, + duration: Int, + gwxAmount: Int, + wxClaimed: Int +) = { makeString([ - "%d%d%d%d%d%d%d%d", # 0 - userNum, # 1 - amount, # 2 - start, # 3 - duration, # 4 - paramK, # 5 - paramB, # 6 - lastUpdTimestamp, #7 - gwxAmount # 8 - ], - SEP) -} - -func formatLockParamsRecord(userNum: String, amount: Int, start: Int, duration: Int, paramK: Int, paramB: Int, gwxAmount: Int) = { - formatLockParamsRecordS( - userNum, # 1 - amount.toString(), # 2 - start.toString(), # 3 - duration.toString(), # 4 - paramK.toString(), # 5 - paramB.toString(), # 6 - lastBlock.timestamp.toString(), # 7 - gwxAmount.toString() # 8 - ) + "%d%d%d%d%d%d%d", # 0 + amount.toString(), # 1 + start.toString(), # 2 + duration.toString(), # 3 + lastBlock.timestamp.toString(), # 4 + gwxAmount.toString(), # 5 + wxClaimed.toString() # 6 + ], SEP) } # mappings -func keyNextUserNum() = "%s__nextUserNum" -func keyUser2NumMapping(userAddress: String) = makeString(["%s%s%s__mapping__user2num", userAddress], SEP) -func keyNum2UserMapping(num: String) = makeString(["%s%s%s__mapping__num2user", num], SEP) - -func keyLockParamUserAmount(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "amount"], SEP) -func keyLockParamStartBlock(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "start"], SEP) -func keyLockParamDuration(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "duration"], SEP) -func keyLockParamK(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "k"], SEP) -func keyLockParamB(userNum: String) = makeString(["%s%d%s__paramByUserNum", userNum, "b"], SEP) -func keyLockParamByPeriodK(userNum: String, period: String) = makeString(["%s%d%s%d__paramByPeriod", userNum, "k", period], SEP) -func keyLockParamByPeriodB(userNum: String, period: String) = makeString(["%s%d%s%d__paramByPeriod", userNum, "b", period], SEP) - -# TODO - consider to concat -func keyLockParamTotalAmount() = "%s%s__stats__activeTotalLocked" -func keyStatsLocksDurationSumInBlocks() = "%s%s__stats__locksDurationSumInBlocks" -func keyStatsLocksCount() = "%s%s__stats__locksCount" -func keyStatsUsersCount() = "%s%s__stats__activeUsersCount" +func keyNextUserNum() = "%s__nextUserNum" +func keyUser2NumMapping(userAddress: String) = makeString(["%s%s%s__mapping__user2num", userAddress], SEP) +func keyNum2UserMapping(num: String) = makeString(["%s%s%s__mapping__num2user", num], SEP) + +func keyLockParamTotalAmount() = "%s%s__stats__activeTotalLocked" +func keyStatsLocksDurationSumInBlocks() = "%s%s__stats__locksDurationSumInBlocks" +func keyStatsLocksCount() = "%s%s__stats__locksCount" +func keyStatsUsersCount() = "%s%s__stats__activeUsersCount" # boost integral -func keyUserBoostEmissionLastINTEGRAL(userNum: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum], SEP) -func keyUserLpBoostEmissionLastINTEGRAL(userNum: String, lpAssetId: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum, lpAssetId], SEP) -func keyUserMaxBoostINTEGRAL(userNum: String) = makeString(["%s%d__maxBoostInt", userNum], SEP) -func keyTotalMaxBoostINTEGRAL() = "%s%s__maxBoostInt__total" -func keyUserBoostAvalaibleToClaimTotal(userNum: String) = makeString(["%s%d__userBoostAvaliableToClaimTotal", userNum], SEP) -func keyUserBoostClaimed(userNum: String) = makeString(["%s%d__userBoostClaimed", userNum], SEP) +func keyUserBoostEmissionLastINTEGRAL(userNum: Int) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum.toString()], SEP) +func keyUserLpBoostEmissionLastINTEGRAL(userNum: Int, lpAssetId: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum.toString(), lpAssetId], SEP) +func keyUserMaxBoostINTEGRAL(userNum: Int) = makeString(["%s%d__maxBoostInt", userNum.toString()], SEP) +func keyTotalMaxBoostINTEGRAL() = "%s%s__maxBoostInt__total" +func keyUserBoostAvalaibleToClaimTotal(userNum: Int) = makeString(["%s%d__userBoostAvaliableToClaimTotal", userNum.toString()], SEP) +func keyUserBoostClaimed(userNum: Int) = makeString(["%s%d__userBoostClaimed", userNum.toString()], SEP) -func keyTotalCachedGwx() = "%s%s__gwxCached__total" -func keyTotalCachedGwxCorrective() = "%s__gwxCachedTotalCorrective" +func keyGwxTotal() = "%s%s__gwx__total" # Voting emission # User vote @@ -265,10 +243,10 @@ func keyStakedByUser(userAddressStr: String, lpAssetIdStr: String) = ["%s%s%s", # GLOBAL VARIABLES # CONSTRUCTOR IS NOT FAILED BECAUSE GLOBAL VARIABLES ARE NOT USED -let factoryContract = readFactoryAddressOrFail() -let factoryCfg = factoryContract.readFactoryCfgOrFail() -let emissionContract = factoryCfg.getEmissionAddressOrFail() -let stakingContract = factoryCfg.getStakingAddressOrFail() +let factoryContract = readFactoryAddressOrFail() +let factoryCfg = factoryContract.readFactoryCfgOrFail() +let emissionContract = factoryCfg.getEmissionAddressOrFail() +let stakingContract = factoryCfg.getStakingAddressOrFail() let gwxRewardContract = factoryCfg.getGwxRewardAddressOrFail() let lpStakingPoolsContract = ["%s", "lpStakingPoolsContract"].makeString(SEP) .getString().valueOrErrorMessage("lp_staking_pools contract address is undefined".wrapErr()) @@ -276,37 +254,52 @@ let lpStakingPoolsContract = ["%s", "lpStakingPoolsContract"].makeString(SEP) let keyVotingEmissionContract = ["%s", "votingEmissionContract"].makeString(SEP) let votingEmissionContract = factoryContract.getStringValue(keyVotingEmissionContract).addressFromStringValue() +# in voting emission contract storage +let keyVotingEmissionRateContract = ["%s", "votingEmissionRateContract"].makeString(SEP) let boostCoeff = emissionContract.invoke("getBoostCoeffREADONLY", [], []).exactAs[Int] -func getTotalCachedGwx(correct: Boolean) = { - let currentEpochUi = votingEmissionContract.getIntegerValue(keyCurrentEpochUi()) +func userNumberByAddressOrFail(userAddress: Address) = { + match this.getString(keyUser2NumMapping(userAddress.toString())) { + case s: String => s.parseInt().valueOrErrorMessage(wrapErr("invalid user number")) + case _: Unit => throwErr("invalid user") + } +} - let keyTargetEpoch = ["%s%s", "totalCachedGwxCorrection__activationEpoch"].makeString(SEP) - let targetEpochOption = this.getInteger(keyTargetEpoch) +func getGwxAmountTotal() = { + this.getInteger(keyGwxTotal()).valueOrElse(0) +} - let totalCachedGwxRaw = this.getInteger(keyTotalCachedGwx()).valueOrElse(0) +func getLockedGwxAmount(userAddress: Address) = { + let functionName = "getLockedGwxAmount" + let votingEmissionRateContract = { + match votingEmissionContract.getString(keyVotingEmissionRateContract) { + case _: Unit => unit + case s: String => addressFromString(s) + } + }.valueOrErrorMessage(wrapErr("invalid voting emission rate address")) + let lockedVotingEmissionRate = votingEmissionContract.invoke(functionName, [userAddress.toString()], []).exactAs[Int] + let lockedVotingEmission = votingEmissionRateContract.invoke(functionName, [userAddress.toString()], []).exactAs[Int] - let isCorrectionActivated = targetEpochOption.isDefined() && (currentEpochUi >= targetEpochOption.value()) - let corrective = if (isCorrectionActivated && correct) then { - this.getInteger(keyTotalCachedGwxCorrective()).valueOrElse(0) - } else 0 + let locked = max([ + lockedVotingEmissionRate, + lockedVotingEmission + ]) - max([0, totalCachedGwxRaw + corrective]) + locked } -func HistoryEntry(type: String, user: String, amount: Int, lockStart: Int, duration: Int, k: Int, b: Int, i: Invocation) = { +func HistoryEntry(type: String, user: String, amount: Int, lockStart: Int, duration: Int, gwxAmount: Int, i: Invocation) = { let historyKEY = makeString(["%s%s%s%s__history", type, user, i.transactionId.toBase58String()], SEP) let historyDATA = makeString([ - "%d%d%d%d%d%d%d", - lastBlock.height.toString(), - lastBlock.timestamp.toString(), - amount.toString(), - lockStart.toString(), - duration.toString(), - k.toString(), - b.toString()], - SEP) + "%d%d%d%d%d%d%d", + lastBlock.height.toString(), + lastBlock.timestamp.toString(), + amount.toString(), + lockStart.toString(), + duration.toString(), + gwxAmount.toString() + ], SEP) StringEntry(historyKEY, historyDATA) } @@ -314,12 +307,12 @@ func StatsEntry(totalLockedInc: Int, durationInc: Int, lockCountInc: Int, usersC let locksDurationSumInBlocksKEY = keyStatsLocksDurationSumInBlocks() let locksCountKEY = keyStatsLocksCount() let usersCountKEY = keyStatsUsersCount() - let totalAmountKEY = keyLockParamTotalAmount() + let totalAmountKEY = keyLockParamTotalAmount() - let locksDurationSumInBlocks = this.ioz(locksDurationSumInBlocksKEY) - let locksCount = this.ioz(locksCountKEY) - let usersCount = this.ioz(usersCountKEY) - let totalAmount = this.ioz(totalAmountKEY) + let locksDurationSumInBlocks = this.getIntOrZero(locksDurationSumInBlocksKEY) + let locksCount = this.getIntOrZero(locksCountKEY) + let usersCount = this.getIntOrZero(usersCountKEY) + let totalAmount = this.getIntOrZero(totalAmountKEY) [IntegerEntry(locksDurationSumInBlocksKEY, locksDurationSumInBlocks + durationInc), IntegerEntry(locksCountKEY, locksCount + lockCountInc), @@ -327,60 +320,33 @@ func StatsEntry(totalLockedInc: Int, durationInc: Int, lockCountInc: Int, usersC IntegerEntry(totalAmountKEY, totalAmount + totalLockedInc)] } -# TODO MOVE INTO MATH CONTRACT -func calcGwxAmount(kRaw: Int, bRaw: Int, h: Int) = { - let SCALE = 1000 # see math contract - (kRaw * h + bRaw) / SCALE -} - -func LockParamsEntry(userAddress: String, userNum: String, amount: Int, start: Int, duration: Int, k: Int, b: Int, period: String) = { - - let userAmountKEY = keyLockParamUserAmount(userNum) - let startBlockKEY = keyLockParamStartBlock(userNum) - let durationKEY = keyLockParamDuration(userNum) - let kKEY = keyLockParamK(userNum) - let bKEY = keyLockParamB(userNum) - let kByPeriodKEY = keyLockParamByPeriodK(userNum, period) - let bByPeriodKEY = keyLockParamByPeriodB(userNum, period) - - # TODO think about moving to another place - let gwxAmount = calcGwxAmount(k, b, height) - [IntegerEntry(userAmountKEY, amount), - IntegerEntry(startBlockKEY, start), - IntegerEntry(durationKEY, duration), - IntegerEntry(kKEY, k), - IntegerEntry(bKEY, b), - IntegerEntry(kByPeriodKEY, k), - IntegerEntry(bByPeriodKEY, b), - StringEntry( # fixme: it does not work for multi lock. The key should include period - keyLockParamsRecord(userAddress), - formatLockParamsRecord(userNum, amount, start, duration, k, b, gwxAmount))] +func LockParamsEntry( + userAddress: Address, + txId: ByteVector, + amount: Int, + start: Int, + duration: Int, + gwxAmount: Int, + wxClaimed: Int +) = { + [ + StringEntry( + keyLockParamsRecord(userAddress, txId), + formatLockParamsRecord(amount, start, duration, gwxAmount, wxClaimed) + ) + ] } func extractOptionalPaymentAmountOrFail(i: Invocation, expectedAssetId: ByteVector) = { - if (i.payments.size() > 1) then throwErr("only one payment is allowed") else + if (i.payments.size() > 1) then throwErr("only one payment is allowed") else if (i.payments.size() == 0) then 0 else let pmt = i.payments[0] if (pmt.assetId.value() != expectedAssetId) then throwErr("invalid asset id in payment") else pmt.amount } -func calcUserGwxAmountAtHeight(userAddress: String, targetHeight: Int) = { - let EMPTY = "empty" - let user2NumMappingKEY = keyUser2NumMapping(userAddress) - let userNum = user2NumMappingKEY.getString().valueOrElse(EMPTY) - - let k = keyLockParamK(userNum).getInteger().valueOrElse(0) - let b = keyLockParamB(userNum).getInteger().valueOrElse(0) - - let gwxAmountCalc = calcGwxAmount(k, b, targetHeight) - let gwxAmount = if (gwxAmountCalc < 0 ) then 0 else gwxAmountCalc - - gwxAmount -} - -func calcCurrentGwxAmount(userAddress: String) = { - userAddress.calcUserGwxAmountAtHeight(height) +func getUserGwxAmountTotal(userAddress: Address) = { + this.getInteger(keyUserGwxAmountTotal(userAddress)).valueOrElse(0) } func getVotingEmissionEpochInfo() = { @@ -530,10 +496,10 @@ func getStakedVotesIntegralsDiff(lpAssetIdStr: String, userAddressStr: String) = # Actions should be applied if wxEmissionPerBlock or boostCoeff changes func refreshBoostEmissionIntegral() = { - let wxEmissionPerBlock = emissionContract.iof(keyEmissionRatePerBlockCurrent()) + let wxEmissionPerBlock = emissionContract.getIntOrFail(keyEmissionRatePerBlockCurrent()) let boostingV2LastUpdateHeightOption = this.getInteger(keyBoostingV2LastUpdateHeight()) let boostingV2IngergalOption = this.getInteger(keyBoostingV2Integral()) - let emissionEnd = emissionContract.iof(keyEmissionEndBlock()) + let emissionEnd = emissionContract.getIntOrFail(keyEmissionEndBlock()) let h = if (height > emissionEnd) then emissionEnd else height let dh = match boostingV2LastUpdateHeightOption { @@ -555,11 +521,9 @@ func refreshBoostEmissionIntegral() = { } func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly: Boolean) = { - let userRecordOption = this.getString(keyLockParamsRecord(userAddressStr)) - if (userRecordOption == unit) then (0, [], "userRecord::is::empty") else + let userAddress = addressFromString(userAddressStr).valueOrErrorMessage(wrapErr("invalid user address")) - let userRecordArray = userRecordOption.value().split(SEP) - let userNumStr = userRecordArray[IdxLockUserNum] + strict userNum = userNumberByAddressOrFail(userAddress) let EMPTYSTR = "empty" # is used from REST let poolWeight = if (lpAssetIdStr != EMPTYSTR) then { @@ -571,12 +535,12 @@ func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly # BOOST INTEGRAL RECALC # updated by claim - let userLpBoostEmissionLastIntegralKEY = keyUserLpBoostEmissionLastINTEGRAL(userNumStr, lpAssetIdStr) + let userLpBoostEmissionLastIntegralKEY = keyUserLpBoostEmissionLastINTEGRAL(userNum, lpAssetIdStr) # updated by lock - let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNumStr) + let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNum) let userBoostEmissionLastIntegral = this.getInteger(userLpBoostEmissionLastIntegralKEY) - .valueOrElse(this.ioz(userBoostEmissionLastIntegralKEY)) + .valueOrElse(this.getIntOrZero(userBoostEmissionLastIntegralKEY)) let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 let userBoostEmissionIntegral = boostEmissionIntegral - userBoostEmissionLastIntegral @@ -607,68 +571,58 @@ func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly (userBoostAvaliableToClaimTotalNew, dataState, debug) } -func lockActions(i: Invocation, duration: Int) = { - let cfgArray = readConfigArrayOrFail() - let assetIdStr = cfgArray[IdxCfgAssetId] - let assetId = assetIdStr.fromBase58String() - let minLockAmount = cfgArray[IdxCfgMinLockAmount].parseIntValue() - let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseIntValue() - let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() - +func lockActions(i: Invocation, durationMonths: Int) = { + let durationMonthsAllowed = [1, 3, 6, 12, 24, 48] + if (!durationMonthsAllowed.containsElement(durationMonths)) then throwErr("invalid duration") else + let duration = durationMonths * blocksInMonth + let assetIdStr = assetId.toBase58String() if (i.payments.size() != 1) then throwErr("invalid payment - exact one payment must be attached") else let pmt = i.payments[0] let pmtAmount = pmt.amount if (assetId != pmt.assetId.value()) then throwErr("invalid asset is in payment - " + assetIdStr + " is expected") else - let nextUserNumKEY = keyNextUserNum() - let userAddressStr = i.caller.toString() + let nextUserNumKEY = keyNextUserNum() + let userAddress = i.caller + let userAddressStr = userAddress.toString() let userIsExisting = getString(keyUser2NumMapping(userAddressStr)).isDefined() let userNumStr = if (userIsExisting) then { getString(keyUser2NumMapping(userAddressStr)).value() } else { # new user - this.iof(nextUserNumKEY).toString() + this.getIntOrFail(nextUserNumKEY).toString() } let userNum = userNumStr.parseIntValue() let lockStart = height - let startBlockKEY = keyLockParamStartBlock(userNumStr) - let durationKEY = keyLockParamDuration(userNumStr) - - let userAmountKEY = keyLockParamUserAmount(userNumStr) - - if ((pmtAmount < minLockAmount) && i.caller != lpStakingPoolsContract) then throwErr("amount is less then minLockAmount=" + minLockAmount.toString()) else + if ((pmtAmount < minLockAmount) && userAddress != lpStakingPoolsContract) then throwErr("amount is less then minLockAmount=" + minLockAmount.toString()) else if (duration < minLockDuration) then throwErr("passed duration is less then minLockDuration=" + minLockDuration.toString()) else if (duration > maxLockDuration) then throwErr("passed duration is greater then maxLockDuration=" + maxLockDuration.toString()) else - if (userIsExisting && (this.iof(startBlockKEY) + this.iof(durationKEY)) >= lockStart) then throwErr("there is an active lock - consider to use increaseLock") else - if (this.ioz(userAmountKEY) > 0) then throwErr("there are locked WXs - consider to use increaseLock " + userAmountKEY) else let coeffX8 = fraction(duration, MULT8, maxLockDuration) let gWxAmountStart = fraction(pmtAmount, coeffX8, MULT8) - let gWxParamsResultList = invoke(mathContract, "calcGwxParamsREADONLY", [gWxAmountStart, lockStart, duration], []).aal() - # TODO check the following macros: gWxParamsInvokeResult[0].exactAs[Int] - let k = gWxParamsResultList[0].ai() - let b = gWxParamsResultList[1].ai() - let period = gWxParamsResultList[2].ai().toString() - - let totalCachedGwxRaw = getTotalCachedGwx(false) + let gwxAmountTotal = getGwxAmountTotal() - let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNumStr) + let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNum) let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 + let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) + + strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes], []) + let arr = if (userIsExisting) then [] else [ IntegerEntry(nextUserNumKEY, userNum + 1), StringEntry(keyUser2NumMapping(userAddressStr), userNumStr), StringEntry(keyNum2UserMapping(userNumStr), userAddressStr) ] - (arr ++ LockParamsEntry(userAddressStr, userNumStr, pmtAmount, lockStart, duration, k, b, period) + (arr ++ LockParamsEntry(userAddress, i.transactionId, pmtAmount, lockStart, duration, gWxAmountStart, 0) ++ StatsEntry(pmtAmount, duration, 1, if (userIsExisting) then 0 else 1) - :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, duration, k, b, i) + :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, duration, gWxAmountStart, i) ++ [ IntegerEntry(userBoostEmissionLastIntegralKEY, boostEmissionIntegral), - IntegerEntry(keyTotalCachedGwx(), totalCachedGwxRaw + gWxAmountStart) + IntegerEntry(keyGwxTotal(), gwxAmountTotal + gWxAmountStart), + IntegerEntry(keyUserGwxAmountTotal(userAddress), userGwxAmountTotal + gWxAmountStart) ], gWxAmountStart) } @@ -705,60 +659,6 @@ func lock(duration: Int) = { (lockActionsResult, unit) } -@Callable(i) -func increaseLock(deltaDuration: Int) = { - let cfgArray = readConfigArrayOrFail() - let assetIdStr = cfgArray[IdxCfgAssetId] - let assetId = assetIdStr.fromBase58String() - let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseIntValue() - let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() - - let pmtAmount = extractOptionalPaymentAmountOrFail(i, assetId) - - let userAddressStr = i.caller.toString() - let userRecordArray = readLockParamsRecordOrFail(userAddressStr) - - let userNumStr = userRecordArray[IdxLockUserNum] - let userAmount = userRecordArray[IdxLockAmount].parseIntValue() - let lockStart = userRecordArray[IdxLockStart].parseIntValue() - let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() - let lockEnd = lockStart + lockDuration - let remainingDuration = max([lockEnd - height, 0]) - - let userAmountNew = userAmount + pmtAmount - let lockDurationNew = remainingDuration + deltaDuration - - if (deltaDuration < 0) then throwErr("duration is less then zero") else - if (lockDurationNew < minLockDuration) then throwErr("lockDurationNew is less then minLockDuration=" + minLockDuration.toString()) else - if (lockDurationNew > maxLockDuration) then throwErr("deltaDuration + existedLockDuration is greater then maxLockDuration=" + maxLockDuration.toString()) else - #if (lockEnd <= height && userAmount > 0) then throwErr("there is an expired lock - need to unlock before new lock") else - - let coeffX8 = fraction(lockDurationNew, MULT8, maxLockDuration) - let gWxAmountStart = fraction(userAmountNew, coeffX8, MULT8) - - strict updateRefActivity = mathContract.invoke("updateReferralActivity", [i.caller.toString(), gWxAmountStart], []) - - let lockStartNew = height - let gWxParamsResultList = invoke(mathContract, "calcGwxParamsREADONLY", [gWxAmountStart, lockStartNew, lockDurationNew], []).aal() - # TODO check the following macros: gWxParamsInvokeResult[0].exactAs[Int] - let k = gWxParamsResultList[0].ai() - let b = gWxParamsResultList[1].ai() - let period = gWxParamsResultList[2].ai().toString() - - let currUserGwx = calcCurrentGwxAmount(userAddressStr) - let gwxDiff = gWxAmountStart - currUserGwx - if (gwxDiff < 0) then throwErr("gwxDiff is less then 0: " + gwxDiff.toString()) else - let totalCachedGwxRaw = getTotalCachedGwx(false) - let totalCachedGwxCorrected = getTotalCachedGwx(true) - - LockParamsEntry(userAddressStr, userNumStr, userAmountNew, lockStartNew, lockDurationNew, k, b, period) - ++ StatsEntry(pmtAmount, deltaDuration, 0, 0) - :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, lockDurationNew, k, b, i) - ++ [ - IntegerEntry(keyTotalCachedGwx(), totalCachedGwxRaw + gwxDiff) - ] -} - @Callable(i) func claimWxBoost(lpAssetIdStr: String, userAddressStr: String) = { if (stakingContract != i.caller) then throwErr("permissions denied") else @@ -774,66 +674,105 @@ func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { } @Callable(i) -func unlock(userAddress: String) = { - let userRecordArray = readLockParamsRecordOrFail(userAddress) +func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let txId = txIdStr.fromBase58String() + let userRecordArray = readLockParamsRecordOrFail(userAddress, txId) + + let userAmount = userRecordArray[IdxLockAmount].parseIntValue() + let lockStart = userRecordArray[IdxLockStart].parseIntValue() + let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() + let lockEnd = lockStart + lockDuration + let wxClaimed = userRecordArray[IdxLockWxClaimed].parseIntValue() + let gwxAmount = userRecordArray[IdxLockGwxAmount].parseIntValue() + + let t = (height - lockStart) / blocksInDay + + let exponent = fraction( + t.toBigInt(), + { 8 * blocksInDay }.toBigInt() * MULT18BI, + lockDuration.toBigInt() + ) + let wxWithdrawable = if (height > lockEnd) then { + userAmount - wxClaimed + } else { + fraction( + userAmount.toBigInt(), + MULT18BI - pow(5.toBigInt(), 1, exponent, SCALE18, SCALE18, DOWN), + MULT18BI + ).toInt() + } - let userNumStr = userRecordArray[IdxLockUserNum] - let userAmount = userRecordArray[IdxLockAmount].parseIntValue() - let lockStart = userRecordArray[IdxLockStart].parseIntValue() - let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() - let lockEnd = lockStart + lockDuration + if (amount > wxWithdrawable) + then throwErr("maximum amount to unlock: " + wxWithdrawable.toString()) + else - let cfgArray = readConfigArrayOrFail() - let assetId = cfgArray[IdxCfgAssetId].fromBase58String() + let gwxBurned = max([ + amount, + fraction(t * blocksInDay, userAmount, maxLockDuration) + ]) + let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") + let lockedGwxAmount = getLockedGwxAmount(userAddress) + + if (gwxRemaining < lockedGwxAmount) + then throwErr("locked gwx amount: " + lockedGwxAmount.toString()) + else - if (lockEnd >= height) then throwErr("wait " + lockEnd.toString() + " to unlock") else if (userAmount <= 0) then throwErr("nothing to unlock") else - let period = mathContract.getInteger(keyNextPeriod()).valueOrElse(0) + let gwxAmountTotal = getGwxAmountTotal() + let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) + + strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes], []) - LockParamsEntry(userAddress, userNumStr, 0, lockStart, lockDuration, 0, 0, period.toString()) + LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + amount) ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed - :+ HistoryEntry("unlock", userAddress, userAmount, lockStart, lockDuration, 0, 0, i) - :+ ScriptTransfer(userAddress.addressFromStringValue(), userAmount, assetId) + :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, gwxBurned, i) + :+ ScriptTransfer(userAddress, userAmount, assetId) + ++ [ + IntegerEntry(keyGwxTotal(), ensurePositive(gwxAmountTotal - gwxBurned, "gwxTotal")), + IntegerEntry(keyUserGwxAmountTotal(userAddress), ensurePositive(userGwxAmountTotal - gwxBurned, "userGwxAmountTotal")) + ] } @Callable(i) -func gwxUserInfoREADONLY(userAddress: String) = { - let gwxAmount = calcCurrentGwxAmount(userAddress) +func gwxUserInfoREADONLY(userAddressStr: String) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let gwxAmount = getUserGwxAmountTotal(userAddress) - ([], [gwxAmount]) + ([], [gwxAmount]) } # for lp_staking_pools @Callable(i) func userMaxDurationREADONLY(userAddressStr: String) = { - let cfgArray = readConfigArrayOrFail() - let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseIntValue() - - let userRecordOption = this.getString(userAddressStr.keyLockParamsRecord()) - if (userRecordOption == unit) then ([], ("lock", maxLockDuration)) else { - let userRecordArray = userRecordOption.value().split(SEP) - let lockStart = userRecordArray[IdxLockStart].parseIntValue() - let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() - let lockEnd = lockStart + lockDuration - let remainingDuration = max([lockEnd - height, 0]) + ([], ("increaseLock", maxLockDuration)) +} - let maxDeltaDuration = maxLockDuration - remainingDuration +# targetHeight is unused +@Callable(i) +func getUserGwxAmountAtHeightREADONLY(userAddressStr: String, targetHeight: Int) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let gwxAmount = userAddress.getUserGwxAmountTotal() - ([], ("increaseLock", maxDeltaDuration)) - } + ([], gwxAmount) } @Callable(i) -func getUserGwxAmountAtHeightREADONLY(userAddress: String, targetHeight: Int) = { - let gwxAmount = userAddress.calcUserGwxAmountAtHeight(targetHeight) +func getUserGwxAmount(userAddressStr: String) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) + let gwxAmount = userAddress.getUserGwxAmountTotal() ([], gwxAmount) } @Callable(i) -func getTotalCachedGwxREADONLY() = { - ([], getTotalCachedGwx(true)) +func getGwxTotalREADONLY() = { + ([], getGwxAmountTotal()) } # call this function when wxEmissionRate or boostCoeff changes diff --git a/ride/boosting_v2.ride b/ride/boosting_v2.ride deleted file mode 100644 index 4122a4ac2..000000000 --- a/ride/boosting_v2.ride +++ /dev/null @@ -1,830 +0,0 @@ -{-# STDLIB_VERSION 6 #-} -{-# CONTENT_TYPE DAPP #-} -{-# SCRIPT_TYPE ACCOUNT #-} - -# Required state entries: -# * "%s%s__config__referralsContractAddress": String -# * "%s__nextUserNum": Int -# * "%s%s__config__factoryAddress": String -# * "%s__config": String ("%s%d%d%d%s__________") -# * "%s__lpStakingPoolsContract": String - -let SEP = "__" -let SCALE8 = 8 -let MULT8 = 100000000 -let POOLWEIGHTMULT = MULT8 -let contractFilename = "boosting.ride" -let SCALE18 = 18 -let MULT18 = 1_000_000_000_000_000_000 -let MULT18BI = MULT18.toBigInt() - -# 24 * 60 -let blocksInDay = 1440 -# 365 * 24 * 60 / 12 -let blocksInMonth = 43800 - -func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") -func throwErr(msg: String) = msg.wrapErr().throw() - -func getStringOrFail(address: Address, key: String) = address.getString(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) -func getIntOrZero(address: Address, key: String) = address.getInteger(key).valueOrElse(0) -func getIntOrDefault(address: Address, key: String, defaultVal: Int) = address.getInteger(key).valueOrElse(defaultVal) -func getIntOrFail(address: Address, key: String) = address.getInteger(key).valueOrErrorMessage(("mandatory this." + key + " is not defined").wrapErr()) - -func abs(val: Int) = if (val < 0) then -val else val - -func ensurePositive(v: Int, m: String|Unit) = { - if (v >= 0) then v else throwErr(m.valueOrElse("value") + " should be positive") -} - -func keyReferralsContractAddress() = ["%s%s", "config", "referralsContractAddress"].makeString(SEP) -let referralsContractAddressOrFail = this.getStringOrFail(keyReferralsContractAddress()).addressFromStringValue() - -let keyReferralProgramName = ["%s%s", "referral", "programName"].makeString(SEP) -let referralProgramNameDefault = "wxlock" -let referralProgramName = this.getString(keyReferralProgramName).valueOrElse(referralProgramNameDefault) - -# FACTORY API -# own factory address key -func keyFactoryAddress() = "%s%s__config__factoryAddress" - -let IdxFactoryCfgStakingDapp = 1 -let IdxFactoryCfgBoostingDapp = 2 -let IdxFactoryCfgIdoDapp = 3 -let IdxFactoryCfgTeamDapp = 4 -let IdxFactoryCfgEmissionDapp = 5 -let IdxFactoryCfgRestDapp = 6 -let IdxFactoryCfgSlippageDapp = 7 -let IdxFactoryCfgDaoDapp = 8 -let IdxFactoryCfgMarketingDapp = 9 -let IdxFactoryCfgGwxRewardDapp = 10 -let IdxFactoryCfgBirdsDapp = 11 - -func keyFactoryCfg() = "%s__factoryConfig" -func keyFactoryLpAssetToPoolContractAddress(lpAssetStr: String) = makeString(["%s%s%s", lpAssetStr, "mappings__lpAsset2PoolContract"], SEP) -func keyFactoryPoolWeight(contractAddress: String) = { ["%s%s", "poolWeight", contractAddress].makeString(SEP) } -func keyFactoryPoolWeightHistory(poolAddress: String, num: Int) = {"%s%s__poolWeight__" + poolAddress + "__" + num.toString()} - -func readFactoryAddressOrFail() = this.getStringOrFail(keyFactoryAddress()).addressFromStringValue() -func readFactoryCfgOrFail(factory: Address) = factory.getStringOrFail(keyFactoryCfg()).split(SEP) -func getBoostingAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgBoostingDapp].addressFromStringValue() -func getEmissionAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgEmissionDapp].addressFromStringValue() -func getStakingAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgStakingDapp].addressFromStringValue() -func getGwxRewardAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgGwxRewardDapp].addressFromStringValue() - -func keyManagerPublicKey() = "%s__managerPublicKey" -func keyManagerVaultAddress() = "%s__managerVaultAddress" - -# EMISSION API -func keyEmissionRatePerBlockCurrent() = "%s%s__ratePerBlock__current" -func keyEmissionRatePerBlockMaxCurrent() = "%s%s__ratePerBlockMax__current" -func keyEmissionStartBlock() = "%s%s__emission__startBlock" -func keyBoostingV2LastUpdateHeight() = "%s%s__boostingV2__startBlock" -func keyBoostingV2Integral() = "%s%s__boostingV2__integral" -func keyEmissionDurationInBlocks() = "%s%s__emission__duration" -func keyEmissionEndBlock() = "%s%s__emission__endBlock" - -# GWX REWARD (MATH) API -func keyNextPergetIntOrDefault() = "%s__nextPeriod" -func keyGwxRewardEmissionStartHeight() = "%s%s__gwxRewardEmissionPart__startHeight" - -# OWN KEYS -let IdxCfgAssetId = 1 -let IdxCfgMinLockAmount = 2 -let IdxCfgMinLockDuration = 3 -let IdxCfgMaxLockDuration = 4 -let IdxCfgMathContract = 5 - -func keyConfig() = {"%s__config"} -func readConfigArrayOrFail() = this.getStringOrFail(keyConfig()).split(SEP) -let cfgArray = readConfigArrayOrFail() -let assetId = cfgArray[IdxCfgAssetId].fromBase58String() -let minLockAmount = cfgArray[IdxCfgMinLockAmount].parseInt().valueOrErrorMessage(wrapErr("invalid min lock amount")) -let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseInt().valueOrErrorMessage(wrapErr("invalid min lock duration")) -let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseInt().valueOrErrorMessage(wrapErr("invalid max lock duration")) -let mathContract = cfgArray[IdxCfgMathContract].addressFromString().valueOrErrorMessage(wrapErr("invalid math contract address")) - -func formatConfigS(assetId: String, minLockAmount: String, minLockDuration: String, maxLockDuration: String, mathContract: String) = { - makeString([ - "%s%d%d%d%s", - assetId, # 1 - minLockAmount, # 2 - minLockDuration, # 3 - maxLockDuration, # 4 - mathContract # 5 - ], - SEP) -} - -func formatConfig(assetId: String, minLockAmount: Int, minLockDuration: Int, maxLockDuration: Int, mathContract: String) = { - formatConfigS( - assetId, # 1 - minLockAmount.toString(), # 2 - minLockDuration.toString(), # 3 - maxLockDuration.toString(), # 4 - mathContract # 5 - ) -} - -func getManagerVaultAddressOrThis() = { - match keyManagerVaultAddress().getString() { - case s:String => s.addressFromStringValue() - case _=> this - } -} - -func managerPublicKeyOrUnit() = { - let managerVaultAddress = getManagerVaultAddressOrThis() - match managerVaultAddress.getString(keyManagerPublicKey()) { - case s: String => s.fromBase58String() - case _: Unit => unit - } -} - -func mustManager(i: Invocation) = { - let pd = "Permission denied".throwErr() - - match managerPublicKeyOrUnit() { - case pk: ByteVector => i.callerPublicKey == pk || pd - case _: Unit => i.caller == this || pd - } -} - -let IdxLockAmount = 1 -let IdxLockStart = 2 -let IdxLockDuration = 3 -let IdxLockLastUpdateTimestamp = 4 -let IdxLockGwxAmount = 5 -let IdxLockWxClaimed = 6 - -func keyLockParamsRecord(userAddress: Address, txId: ByteVector) = makeString(["%s%s%s__lock", userAddress.toString(), txId.toBase58String()], SEP) -func readLockParamsRecordOrFail(userAddress: Address, txId: ByteVector) = this.getStringOrFail(keyLockParamsRecord(userAddress, txId)).split(SEP) - -func keyUserGwxAmountTotal(userAddress: Address) = makeString(["%s%s__gwxAmountTotal", userAddress.toString()], SEP) - -func formatLockParamsRecord( - amount: Int, - start: Int, - duration: Int, - gwxAmount: Int, - wxClaimed: Int -) = { - makeString([ - "%d%d%d%d%d%d%d", # 0 - amount.toString(), # 1 - start.toString(), # 2 - duration.toString(), # 3 - lastBlock.timestamp.toString(), # 4 - gwxAmount.toString(), # 5 - wxClaimed.toString() # 6 - ], SEP) -} - -# mappings -func keyNextUserNum() = "%s__nextUserNum" -func keyUser2NumMapping(userAddress: String) = makeString(["%s%s%s__mapping__user2num", userAddress], SEP) -func keyNum2UserMapping(num: String) = makeString(["%s%s%s__mapping__num2user", num], SEP) - -func keyLockParamTotalAmount() = "%s%s__stats__activeTotalLocked" -func keyStatsLocksDurationSumInBlocks() = "%s%s__stats__locksDurationSumInBlocks" -func keyStatsLocksCount() = "%s%s__stats__locksCount" -func keyStatsUsersCount() = "%s%s__stats__activeUsersCount" - -# boost integral -func keyUserBoostEmissionLastINTEGRAL(userNum: Int) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum.toString()], SEP) -func keyUserLpBoostEmissionLastINTEGRAL(userNum: Int, lpAssetId: String) = makeString(["%s%d__userBoostEmissionLastIntV2", userNum.toString(), lpAssetId], SEP) -func keyUserMaxBoostINTEGRAL(userNum: Int) = makeString(["%s%d__maxBoostInt", userNum.toString()], SEP) -func keyTotalMaxBoostINTEGRAL() = "%s%s__maxBoostInt__total" -func keyUserBoostAvalaibleToClaimTotal(userNum: Int) = makeString(["%s%d__userBoostAvaliableToClaimTotal", userNum.toString()], SEP) -func keyUserBoostClaimed(userNum: Int) = makeString(["%s%d__userBoostClaimed", userNum.toString()], SEP) - -func keyGwxTotal() = "%s%s__gwx__total" - -# Voting emission -# User vote -func keyVote(amountAssetId: String, priceAssetId: String, address: Address, epoch: Int) = { - ["%s%s%s%s%d", "vote", amountAssetId, priceAssetId, address.toString(), epoch.toString()].makeString(SEP) -} -func keyStartHeightByEpoch(epoch: Int) = ["%s%d", "startHeight", epoch.toString()].makeString(SEP) -func keyCurrentEpochUi() = ["%s", "currentEpochUi"].makeString(SEP) - -# Initial value is stored on voting emission account -# Following values are stored on this account -func keyVotingResultStaked(lpAssetIdStr: String, epoch: Int) = { - ["%s%s%d", "votingResultStaked", lpAssetIdStr, epoch.toString()].makeString(SEP) -} - -# this -# We need to save integral to handle changing value -func keyVotingResultStakedIntegral(lpAssetIdStr: String, epoch: Int) = { - ["%s%s%d", "votingResultStakedIntegral", lpAssetIdStr, epoch.toString()].makeString(SEP) -} -# Height at which voting result staked was last changed -func keyVotingResultStakedLastUpdateHeight(lpAssetIdStr: String, epoch: Int) = { - ["%s%s%d", "votingResultStakedIntegralLastUpdateHeight", lpAssetIdStr, epoch.toString()].makeString(SEP) -} -# Last integral value used by user -# This value is needed to calculate unclaimed boost -func keyVotingResultStakedIntegralLast(lpAssetIdStr: String, address: Address, epoch: Int) = { - ["%s%s%s%d", "votingResultStakedIntegralLast", lpAssetIdStr, address.toString(), epoch.toString()].makeString(SEP) -} -func keyVoteStakedIntegral(lpAssetIdStr: String, address: Address, epoch: Int) = { - ["%s%s%s%d", "voteStakedIntegral", lpAssetIdStr, address.toString(), epoch.toString()].makeString(SEP) -} -func keyVoteStakedLastUpdateHeight(lpAssetIdStr: String, address: Address, epoch: Int) = { - ["%s%s%s%d", "voteStakedIntegralLastUpdateHeight", lpAssetIdStr, address.toString(), epoch.toString()].makeString(SEP) -} -func keyVoteStakedIntegralLast(lpAssetIdStr: String, address: Address, epoch: Int) = { - ["%s%s%s%d", "voteStakedIntegralLast", lpAssetIdStr, address.toString(), epoch.toString()].makeString(SEP) -} - -# staking -func keyStakedByUser(userAddressStr: String, lpAssetIdStr: String) = ["%s%s%s", "staked", userAddressStr, lpAssetIdStr].makeString(SEP) - -# GLOBAL VARIABLES -# CONSTRUCTOR IS NOT FAILED BECAUSE GLOBAL VARIABLES ARE NOT USED -let factoryContract = readFactoryAddressOrFail() -let factoryCfg = factoryContract.readFactoryCfgOrFail() -let emissionContract = factoryCfg.getEmissionAddressOrFail() -let stakingContract = factoryCfg.getStakingAddressOrFail() -let gwxRewardContract = factoryCfg.getGwxRewardAddressOrFail() -let lpStakingPoolsContract = ["%s", "lpStakingPoolsContract"].makeString(SEP) - .getString().valueOrErrorMessage("lp_staking_pools contract address is undefined".wrapErr()) - .addressFromString().valueOrErrorMessage("invalid lp_staking_pools contract address".wrapErr()) - -let keyVotingEmissionContract = ["%s", "votingEmissionContract"].makeString(SEP) -let votingEmissionContract = factoryContract.getStringValue(keyVotingEmissionContract).addressFromStringValue() -# in voting emission contract storage -let keyVotingEmissionRateContract = ["%s", "votingEmissionRateContract"].makeString(SEP) - -let boostCoeff = emissionContract.invoke("getBoostCoeffREADONLY", [], []).exactAs[Int] - -func userNumberByAddressOrFail(userAddress: Address) = { - match this.getString(keyUser2NumMapping(userAddress.toString())) { - case s: String => s.parseInt().valueOrErrorMessage(wrapErr("invalid user number")) - case _: Unit => throwErr("invalid user") - } -} - -func getGwxAmountTotal() = { - this.getInteger(keyGwxTotal()).valueOrElse(0) -} - -func getLockedGwxAmount(userAddress: Address) = { - let functionName = "getLockedGwxAmount" - let votingEmissionRateContract = { - match votingEmissionContract.getString(keyVotingEmissionRateContract) { - case _: Unit => unit - case s: String => addressFromString(s) - } - }.valueOrErrorMessage(wrapErr("invalid voting emission rate address")) - let lockedVotingEmissionRate = votingEmissionContract.invoke(functionName, [userAddress.toString()], []).exactAs[Int] - let lockedVotingEmission = votingEmissionRateContract.invoke(functionName, [userAddress.toString()], []).exactAs[Int] - - let locked = max([ - lockedVotingEmissionRate, - lockedVotingEmission - ]) - - locked -} - -func HistoryEntry(type: String, user: String, amount: Int, lockStart: Int, duration: Int, gwxAmount: Int, i: Invocation) = { - let historyKEY = makeString(["%s%s%s%s__history", type, user, i.transactionId.toBase58String()], SEP) - let historyDATA = makeString([ - "%d%d%d%d%d%d%d", - lastBlock.height.toString(), - lastBlock.timestamp.toString(), - amount.toString(), - lockStart.toString(), - duration.toString(), - gwxAmount.toString() - ], SEP) - StringEntry(historyKEY, historyDATA) -} - -func StatsEntry(totalLockedInc: Int, durationInc: Int, lockCountInc: Int, usersCountInc: Int) = { - let locksDurationSumInBlocksKEY = keyStatsLocksDurationSumInBlocks() - let locksCountKEY = keyStatsLocksCount() - let usersCountKEY = keyStatsUsersCount() - let totalAmountKEY = keyLockParamTotalAmount() - - let locksDurationSumInBlocks = this.getIntOrZero(locksDurationSumInBlocksKEY) - let locksCount = this.getIntOrZero(locksCountKEY) - let usersCount = this.getIntOrZero(usersCountKEY) - let totalAmount = this.getIntOrZero(totalAmountKEY) - - [IntegerEntry(locksDurationSumInBlocksKEY, locksDurationSumInBlocks + durationInc), - IntegerEntry(locksCountKEY, locksCount + lockCountInc), - IntegerEntry(usersCountKEY, usersCount + usersCountInc), - IntegerEntry(totalAmountKEY, totalAmount + totalLockedInc)] -} - -func LockParamsEntry( - userAddress: Address, - txId: ByteVector, - amount: Int, - start: Int, - duration: Int, - gwxAmount: Int, - wxClaimed: Int -) = { - [ - StringEntry( - keyLockParamsRecord(userAddress, txId), - formatLockParamsRecord(amount, start, duration, gwxAmount, wxClaimed) - ) - ] -} - -func extractOptionalPaymentAmountOrFail(i: Invocation, expectedAssetId: ByteVector) = { - if (i.payments.size() > 1) then throwErr("only one payment is allowed") else - if (i.payments.size() == 0) then 0 else - let pmt = i.payments[0] - if (pmt.assetId.value() != expectedAssetId) then throwErr("invalid asset id in payment") else - pmt.amount -} - -func getUserGwxAmountTotal(userAddress: Address) = { - this.getInteger(keyUserGwxAmountTotal(userAddress)).valueOrElse(0) -} - -func getVotingEmissionEpochInfo() = { - let (currentEpochUi, lastFinalizedEpoch) = { - let currentEpochUi = votingEmissionContract.getInteger(keyCurrentEpochUi()).value() - let lastFinalizedEpoch = currentEpochUi - 1 - if (lastFinalizedEpoch < 0) then "invalid epoch".throwErr() else (currentEpochUi, lastFinalizedEpoch) - } - - let currentEpochStartHeight = votingEmissionContract.getInteger(currentEpochUi.keyStartHeightByEpoch()).value() - - (lastFinalizedEpoch, currentEpochStartHeight) -} - -func getPoolAssetsByLpAssetId(lpAssetIdStr: String) = { - let idxAmountAssetId = 4 - let idxPriceAssetId = 5 - let poolCfg = factoryContract.invoke("getPoolConfigByLpAssetIdREADONLY", [lpAssetIdStr], []).exactAs[List[Any]] - let amountAssetId = poolCfg[idxAmountAssetId].exactAs[String] - let priceAssetId = poolCfg[idxPriceAssetId].exactAs[String] - - (amountAssetId, priceAssetId) -} - -func getUserVoteFinalized(lpAssetIdStr: String, userAddressStr: String) = { - let userAddress = userAddressStr.addressFromStringValue() - let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() - let (amountAssetId, priceAssetId) = lpAssetIdStr.getPoolAssetsByLpAssetId() - let userVoteKey = keyVote(amountAssetId, priceAssetId, userAddress, lastFinalizedEpoch) - let userVote = votingEmissionContract.getInteger(userVoteKey).valueOrElse(0) - - userVote -} - -func getUserVoteStaked(lpAssetIdStr: String, userAddressStr: String) = { - let stakedByUser = stakingContract.getInteger(keyStakedByUser(userAddressStr, lpAssetIdStr)).valueOrElse(0) - let userVote = lpAssetIdStr.getUserVoteFinalized(userAddressStr) - - if (stakedByUser == 0) then 0 else userVote -} - -func getVotingResultStaked(lpAssetIdStr: String) = { - let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() - let votingResultStakedStart = votingEmissionContract.getInteger(lpAssetIdStr.keyVotingResultStaked(lastFinalizedEpoch)).valueOrElse(0) - let votingResultStaked = this.getInteger(lpAssetIdStr.keyVotingResultStaked(lastFinalizedEpoch)).valueOrElse(votingResultStakedStart) - - votingResultStaked -} - -func getVotingResultStakedIntegral(lpAssetIdStr: String) = { - let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() - let votingResultStaked = lpAssetIdStr.getVotingResultStaked() - let votingResultStakedIntegralPrev = this.getInteger( - lpAssetIdStr.keyVotingResultStakedIntegral(lastFinalizedEpoch) - ).valueOrElse(0) - let votingResultStakedLastUpdateHeight = this.getInteger( - lpAssetIdStr.keyVotingResultStakedLastUpdateHeight(lastFinalizedEpoch) - ).valueOrElse(currentEpochStartHeight) - let votingResultStakedIntegralDh = height - votingResultStakedLastUpdateHeight - let votingResultStakedIntegral = votingResultStakedIntegralDh * votingResultStaked + votingResultStakedIntegralPrev - - votingResultStakedIntegral -} - -# Voting result staked is changing only when staked vote is changing -# So this function is called in refreshVoteStakedIntegral -func refreshVotingResultStakedIntegral( - lpAssetIdStr: String, - stakedVoteDelta: Int -) = { - let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() - let votingResultStaked = lpAssetIdStr.getVotingResultStaked() - let votingResultStakedNew = votingResultStaked + stakedVoteDelta - let votingResultStakedIntegral = lpAssetIdStr.getVotingResultStakedIntegral() - - [ - IntegerEntry(lpAssetIdStr.keyVotingResultStaked(lastFinalizedEpoch), votingResultStakedNew), - IntegerEntry(lpAssetIdStr.keyVotingResultStakedLastUpdateHeight(lastFinalizedEpoch), height), - IntegerEntry(lpAssetIdStr.keyVotingResultStakedIntegral(lastFinalizedEpoch), votingResultStakedIntegral) - ] -} - -func getUserVoteStakedIntegral(lpAssetIdStr: String, userAddressStr: String) = { - let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() - let userAddress = userAddressStr.addressFromStringValue() - let userVoteStaked = lpAssetIdStr.getUserVoteStaked(userAddressStr) - let userVoteStakedIntegralPrev = this.getInteger( - lpAssetIdStr.keyVoteStakedIntegral(userAddress, lastFinalizedEpoch) - ).valueOrElse(0) - let userVoteStakedLastUpdateHeight = this.getInteger( - lpAssetIdStr.keyVoteStakedLastUpdateHeight(userAddress, lastFinalizedEpoch) - ).valueOrElse(currentEpochStartHeight) - let userVoteStakedIntegralDh = height - userVoteStakedLastUpdateHeight - let userVoteStakedIntegral = userVoteStakedIntegralDh * userVoteStaked + userVoteStakedIntegralPrev - - userVoteStakedIntegral -} - -func refreshVoteStakedIntegral( - lpAssetIdStr: String, - userAddressStr: String, - edge: Boolean # true: add, false: remove -) = { - let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() - let userAddress = userAddressStr.addressFromStringValue() - - let userVoteFinalized = lpAssetIdStr.getUserVoteFinalized(userAddressStr) - - let actions = if (userVoteFinalized == 0) then [] else { - let stakedVoteDelta = if (edge) then userVoteFinalized else -userVoteFinalized - let votingResultActions = lpAssetIdStr.refreshVotingResultStakedIntegral(stakedVoteDelta) - - let userVoteStakedIntegral = lpAssetIdStr.getUserVoteStakedIntegral(userAddressStr) - let voteActions = [ - IntegerEntry(lpAssetIdStr.keyVoteStakedLastUpdateHeight(userAddress, lastFinalizedEpoch), height), - IntegerEntry(lpAssetIdStr.keyVoteStakedIntegral(userAddress, lastFinalizedEpoch), userVoteStakedIntegral) - ] - - votingResultActions ++ voteActions - } - - actions -} - -func getStakedVotesIntegralsDiff(lpAssetIdStr: String, userAddressStr: String) = { - let (lastFinalizedEpoch, currentEpochStartHeight) = getVotingEmissionEpochInfo() - let userAddress = userAddressStr.addressFromStringValue() - - let userVoteStakedIntegralLastKey = lpAssetIdStr.keyVoteStakedIntegralLast(userAddress, lastFinalizedEpoch) - let userVoteStakedIntegralLast = this.getInteger(userVoteStakedIntegralLastKey).valueOrElse(0) - let votingResultStakedIntegralLastKey = lpAssetIdStr.keyVotingResultStakedIntegralLast(userAddress, lastFinalizedEpoch) - let votingResultStakedIntegralLast = this.getInteger(votingResultStakedIntegralLastKey).valueOrElse(0) - let userVoteStakedIntegral = lpAssetIdStr.getUserVoteStakedIntegral(userAddressStr) - let votingResultStakedIntegral = lpAssetIdStr.getVotingResultStakedIntegral() - let userVoteStakedIntegralDiff = userVoteStakedIntegral - userVoteStakedIntegralLast - let votingResultStakedIntegralDiff = votingResultStakedIntegral - votingResultStakedIntegralLast - - ( - [ - IntegerEntry(userVoteStakedIntegralLastKey, userVoteStakedIntegral), - IntegerEntry(votingResultStakedIntegralLastKey, votingResultStakedIntegral) - ], - userVoteStakedIntegralDiff, - votingResultStakedIntegralDiff - ) -} - -# Actions should be applied if wxEmissionPerBlock or boostCoeff changes -func refreshBoostEmissionIntegral() = { - let wxEmissionPerBlock = emissionContract.getIntOrFail(keyEmissionRatePerBlockCurrent()) - let boostingV2LastUpdateHeightOption = this.getInteger(keyBoostingV2LastUpdateHeight()) - let boostingV2IngergalOption = this.getInteger(keyBoostingV2Integral()) - let emissionEnd = emissionContract.getIntOrFail(keyEmissionEndBlock()) - let h = if (height > emissionEnd) then emissionEnd else height - - let dh = match boostingV2LastUpdateHeightOption { - case lastUpdateHeight: Int => max([h - lastUpdateHeight, 0]) - case _: Unit => 0 - } - - let boostEmissionPerBlock = wxEmissionPerBlock * (boostCoeff - 1) / boostCoeff - let boostEmissionIntegralPrev = boostingV2IngergalOption.valueOrElse(0) - let boostEmissionIntegral = boostEmissionPerBlock * dh + boostEmissionIntegralPrev - - ( - [ - IntegerEntry(keyBoostingV2Integral(), boostEmissionIntegral), - IntegerEntry(keyBoostingV2LastUpdateHeight(), height) - ], - boostEmissionIntegral - ) -} - -func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly: Boolean) = { - let userAddress = addressFromString(userAddressStr).valueOrErrorMessage(wrapErr("invalid user address")) - - strict userNum = userNumberByAddressOrFail(userAddress) - - let EMPTYSTR = "empty" # is used from REST - let poolWeight = if (lpAssetIdStr != EMPTYSTR) then { - let poolAddressStr = factoryContract.getString(keyFactoryLpAssetToPoolContractAddress(lpAssetIdStr)).valueOrErrorMessage(("unsupported lp asset " + lpAssetIdStr).wrapErr()) - factoryContract.getIntegerValue(poolAddressStr.keyFactoryPoolWeight()) - } else { - if (readOnly) then 0 else throwErr("not readonly mode: unsupported lp asset " + lpAssetIdStr) - } - - # BOOST INTEGRAL RECALC - # updated by claim - let userLpBoostEmissionLastIntegralKEY = keyUserLpBoostEmissionLastINTEGRAL(userNum, lpAssetIdStr) - # updated by lock - let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNum) - - let userBoostEmissionLastIntegral = this.getInteger(userLpBoostEmissionLastIntegralKEY) - .valueOrElse(this.getIntOrZero(userBoostEmissionLastIntegralKEY)) - - let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 - let userBoostEmissionIntegral = boostEmissionIntegral - userBoostEmissionLastIntegral - - if (userBoostEmissionIntegral < 0) then throwErr("wrong calculations") else - - let (stakedVotesIntegralsActions, userVoteIntegralDiff, totalVotesIntegralDiff) = getStakedVotesIntegralsDiff(lpAssetIdStr, userAddressStr) - - let poolUserBoostEmissionIntegral = fraction(userBoostEmissionIntegral, poolWeight, POOLWEIGHTMULT) - - let userBoostAvaliableToClaimTotalNew = if (totalVotesIntegralDiff == 0) then 0 else { - fraction(poolUserBoostEmissionIntegral, userVoteIntegralDiff, totalVotesIntegralDiff) - } - - # BOOST INTEGRAL - let dataState = [ - IntegerEntry(userLpBoostEmissionLastIntegralKEY, boostEmissionIntegral) - ] ++ stakedVotesIntegralsActions - - let debug = [ - userBoostEmissionLastIntegral.toString(), - userBoostEmissionIntegral.toString(), - poolWeight.toString(), - userVoteIntegralDiff.toString(), - totalVotesIntegralDiff.toString() - ].makeString(":") - - (userBoostAvaliableToClaimTotalNew, dataState, debug) -} - -func lockActions(i: Invocation, durationMonths: Int) = { - let durationMonthsAllowed = [1, 3, 6, 12, 24, 48] - if (!durationMonthsAllowed.containsElement(durationMonths)) then throwErr("invalid duration") else - let duration = durationMonths * blocksInMonth - let assetIdStr = assetId.toBase58String() - if (i.payments.size() != 1) then throwErr("invalid payment - exact one payment must be attached") else - let pmt = i.payments[0] - let pmtAmount = pmt.amount - - if (assetId != pmt.assetId.value()) then throwErr("invalid asset is in payment - " + assetIdStr + " is expected") else - - let nextUserNumKEY = keyNextUserNum() - let userAddress = i.caller - let userAddressStr = userAddress.toString() - - let userIsExisting = getString(keyUser2NumMapping(userAddressStr)).isDefined() - let userNumStr = if (userIsExisting) then { - getString(keyUser2NumMapping(userAddressStr)).value() - } else { # new user - this.getIntOrFail(nextUserNumKEY).toString() - } - let userNum = userNumStr.parseIntValue() - let lockStart = height - - if ((pmtAmount < minLockAmount) && userAddress != lpStakingPoolsContract) then throwErr("amount is less then minLockAmount=" + minLockAmount.toString()) else - if (duration < minLockDuration) then throwErr("passed duration is less then minLockDuration=" + minLockDuration.toString()) else - if (duration > maxLockDuration) then throwErr("passed duration is greater then maxLockDuration=" + maxLockDuration.toString()) else - - let coeffX8 = fraction(duration, MULT8, maxLockDuration) - let gWxAmountStart = fraction(pmtAmount, coeffX8, MULT8) - - let gwxAmountTotal = getGwxAmountTotal() - - let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNum) - let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 - - let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) - - strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes], []) - - let arr = if (userIsExisting) then [] else [ - IntegerEntry(nextUserNumKEY, userNum + 1), - StringEntry(keyUser2NumMapping(userAddressStr), userNumStr), - StringEntry(keyNum2UserMapping(userNumStr), userAddressStr) - ] - (arr ++ LockParamsEntry(userAddress, i.transactionId, pmtAmount, lockStart, duration, gWxAmountStart, 0) - ++ StatsEntry(pmtAmount, duration, 1, if (userIsExisting) then 0 else 1) - :+ HistoryEntry("lock", userAddressStr, pmtAmount, lockStart, duration, gWxAmountStart, i) - ++ [ - IntegerEntry(userBoostEmissionLastIntegralKEY, boostEmissionIntegral), - IntegerEntry(keyGwxTotal(), gwxAmountTotal + gWxAmountStart), - IntegerEntry(keyUserGwxAmountTotal(userAddress), userGwxAmountTotal + gWxAmountStart) - ], gWxAmountStart) -} - -@Callable(i) -func constructor(factoryAddressStr: String, lockAssetIdStr: String, minLockAmount: Int, minDuration: Int, maxDuration: Int, mathContract: String) = { - strict checkCaller = i.mustManager() - - [IntegerEntry(keyNextUserNum(), 0), - StringEntry( - keyConfig(), - formatConfig(lockAssetIdStr, minLockAmount, minDuration, maxDuration, mathContract)), - StringEntry(keyFactoryAddress(), factoryAddressStr) - ] - ++ StatsEntry(0, 0, 0, 0) -} - -@Callable(i) -func lockRef(duration: Int, referrerAddress: String, signature: ByteVector) = { - let (lockActionsResult, gWxAmountStart) = i.lockActions(duration) - let referralAddress = i.caller.toString() - strict refInv = if (referrerAddress == "" || signature == base58'') then unit else { - referralsContractAddressOrFail.invoke("createPair", [referralProgramName, referrerAddress, referralAddress, signature], []) - } - strict updateRefActivity = mathContract.invoke("updateReferralActivity", [i.caller.toString(), gWxAmountStart], []) - - (lockActionsResult, unit) -} - -@Callable(i) -func lock(duration: Int) = { - let (lockActionsResult, gWxAmountStart) = i.lockActions(duration) - strict updateRefActivity = mathContract.invoke("updateReferralActivity", [i.caller.toString(), gWxAmountStart], []) - - (lockActionsResult, unit) -} - -@Callable(i) -func claimWxBoost(lpAssetIdStr: String, userAddressStr: String) = { - if (stakingContract != i.caller) then throwErr("permissions denied") else - let (userBoostAvailable, dataState, debug) = internalClaimWxBoost(lpAssetIdStr, userAddressStr, false) - - (dataState, [userBoostAvailable]) -} - -@Callable(i) -func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { - let (userBoostAvailable, dataState, debug) = internalClaimWxBoost(lpAssetIdStr, userAddressStr, true) - ([], [userBoostAvailable, debug]) -} - -@Callable(i) -func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { - let userAddress = userAddressStr.addressFromString() - .valueOrErrorMessage(wrapErr("invalid user address")) - let txId = txIdStr.fromBase58String() - let userRecordArray = readLockParamsRecordOrFail(userAddress, txId) - - let userAmount = userRecordArray[IdxLockAmount].parseIntValue() - let lockStart = userRecordArray[IdxLockStart].parseIntValue() - let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() - let lockEnd = lockStart + lockDuration - let wxClaimed = userRecordArray[IdxLockWxClaimed].parseIntValue() - let gwxAmount = userRecordArray[IdxLockGwxAmount].parseIntValue() - - let t = (height - lockStart) / blocksInDay - - let exponent = fraction( - t.toBigInt(), - { 8 * blocksInDay }.toBigInt() * MULT18BI, - lockDuration.toBigInt() - ) - let wxWithdrawable = if (height > lockEnd) then { - userAmount - wxClaimed - } else { - fraction( - userAmount.toBigInt(), - MULT18BI - pow(5.toBigInt(), 1, exponent, SCALE18, SCALE18, DOWN), - MULT18BI - ).toInt() - } - - if (amount > wxWithdrawable) - then throwErr("maximum amount to unlock: " + wxWithdrawable.toString()) - else - - let gwxBurned = max([ - amount, - fraction(t * blocksInDay, userAmount, maxLockDuration) - ]) - let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") - let lockedGwxAmount = getLockedGwxAmount(userAddress) - - if (gwxRemaining < lockedGwxAmount) - then throwErr("locked gwx amount: " + lockedGwxAmount.toString()) - else - - if (userAmount <= 0) then throwErr("nothing to unlock") else - - let gwxAmountTotal = getGwxAmountTotal() - let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) - - strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes], []) - - LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + amount) - ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed - :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, gwxBurned, i) - :+ ScriptTransfer(userAddress, userAmount, assetId) - ++ [ - IntegerEntry(keyGwxTotal(), ensurePositive(gwxAmountTotal - gwxBurned, "gwxTotal")), - IntegerEntry(keyUserGwxAmountTotal(userAddress), ensurePositive(userGwxAmountTotal - gwxBurned, "userGwxAmountTotal")) - ] -} - -@Callable(i) -func gwxUserInfoREADONLY(userAddressStr: String) = { - let userAddress = userAddressStr.addressFromString() - .valueOrErrorMessage(wrapErr("invalid user address")) - let gwxAmount = getUserGwxAmountTotal(userAddress) - - ([], [gwxAmount]) -} - -# for lp_staking_pools -@Callable(i) -func userMaxDurationREADONLY(userAddressStr: String) = { - ([], ("increaseLock", maxLockDuration)) -} - -# targetHeight is unused -@Callable(i) -func getUserGwxAmountAtHeightREADONLY(userAddressStr: String, targetHeight: Int) = { - let userAddress = userAddressStr.addressFromString() - .valueOrErrorMessage(wrapErr("invalid user address")) - let gwxAmount = userAddress.getUserGwxAmountTotal() - - ([], gwxAmount) -} - -@Callable(i) -func getUserGwxAmount(userAddressStr: String) = { - let userAddress = userAddressStr.addressFromString() - .valueOrErrorMessage(wrapErr("invalid user address")) - let gwxAmount = userAddress.getUserGwxAmountTotal() - - ([], gwxAmount) -} - -@Callable(i) -func getGwxTotalREADONLY() = { - ([], getGwxAmountTotal()) -} - -# call this function when wxEmissionRate or boostCoeff changes -@Callable(i) -func onBoostEmissionUpdate() = { - strict checkCaller = i.caller == emissionContract || i.mustManager() - refreshBoostEmissionIntegral() -} - -# Staked vote = 0 if staked amount = 0 -# Staked vote = last finalized vote if staked amount ≥ 0 -# So, stakedVote = [stakedAmount ≥ 0] * lastFinalizedVote -# └ this is the Iverson bracket, not the array's one -# Staked vote updates when staked amount goes from zero to non-zero or vice versa -# Staked vote integral and voting result integral should be updated when it happens -# ╭­ edge = true (rising) -# staked amount > 0: ┌───┐ -# staked amount = 0: ───┘ └─── -# ╰ edge = false (falling) -@Callable(i) -func onStakedVoteUpdate(lpAssetIdStr: String, userAddressStr: String, edge: Boolean) = { - strict checkCaller = i.caller == stakingContract || i.mustManager() - let actions = lpAssetIdStr.refreshVoteStakedIntegral(userAddressStr, edge) - - (actions, unit) -} - -@Callable(i) -func getVotingResultStakedREADONLY(lpAssetIdStr: String) = { - ([], lpAssetIdStr.getVotingResultStaked()) -} - -@Callable(i) -func getVotingResultStakedIntegralREADONLY(lpAssetIdStr: String) = { - ([], lpAssetIdStr.getVotingResultStakedIntegral()) -} - -@Callable(i) -func getUserVoteFinalizedREADONLY(lpAssetIdStr: String, userAddressStr: String) = { - ([], lpAssetIdStr.getUserVoteFinalized(userAddressStr)) -} - -@Callable(i) -func getUserVoteStakedIntegralREADONLY(lpAssetIdStr: String, userAddressStr: String) = { - ([], lpAssetIdStr.getUserVoteStakedIntegral(userAddressStr)) -} - -@Verifier(tx) -func verify() = { - let targetPublicKey = match managerPublicKeyOrUnit() { - case pk: ByteVector => pk - case _: Unit => tx.senderPublicKey - } - sigVerify(tx.bodyBytes, tx.proofs[0], targetPublicKey) -} diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index 179dd9da3..d666127df 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -9,12 +9,16 @@ # * "%s%s__config__referralsContractAddress": String # * "%s__latestPeriod": Integer +# TODO: seems like we can use keyGwxRewardEmissionStartHeight for integral start height + let SEP = "__" let SCALE = 1000 let MULT8 = 1_0000_0000 let zeroBigInt = 0.toBigInt() let processingStageTotal = 0 let processingStageShares = 1 +let MULT18 = 1_000_000_000_000_000_000 +let MULT18BI = MULT18.toBigInt() let wavesString = "WAVES" @@ -143,68 +147,9 @@ func keyGwxHoldersRewardNext() = "%s%s__gwxHoldersReward__next" # factory contract key func keyPoolWeightVirtual() = "%s%s__poolWeight__GWXvirtualPOOL" -# user to be processed next time within nextProcessedPeriod -func keyNextProcessedUser() = "%s__nextProcessedUser" - -# latest finalized period (next matcher/emission payment will go to nextPeriod) -func keyLatestPeriod() = "%s__latestPeriod" - -# period to place incoming payment next time (incremented by deposit() callable) -func keyNextPeriod() = "%s__nextPeriod" - -# stage of processing 0|Unit - calculate total weight, 1 - calculate shares -func keyProcessingStage() = "%s__processingStage" - -# period to be processed next time (incomplete) -func keyNextProcessedPeriod() = "%s__nextProcessedPeriod" - func keyUserUnclaimed(userIndex: Int) = ["%s%d", "userUnclaimed", userIndex.toString()].makeString(SEP) -# next (unclaimed yet) period for user -func keyNextUnlaimedPeriodOfUser(userIndex: Int) = - makeString(["%s%d__nextClaimedPeriod", userIndex.toString()], SEP) - -# period to get K and B values for given user during weight calculation -func keyLastProcessedPeriodOfUser(userIndex: Int) = - makeString(["%s%d__lastProcessedPeriod", userIndex.toString()], SEP) - -func keyHeightForPeriod(period: Int) = - makeString(["%s%d__startHeightForPeriod", period.toString()], SEP) - -# amount of WX to distribute between all users (as emission 5% reward) for period -func keyAuxEmissionRewardForPeriod(period: Int) = - makeString(["%s%d__auxEmissionReward", period.toString()], SEP) - -# amount of WX to distribute between all users (as matcher comissions reward) for period -func keyTotalAmountForPeriod(period: Int) = - makeString(["%s%d__totalAmountForPeriod", period.toString()], SEP) - -# rewards from matcher and from emission for last finalized period -func keyLastPayoutInfo() = "%s__lastPayoutInfo" - -func PeriodPayoutInfo(period: Int, matcherReward: Int, emissionReward: Int) = - makeString(["%d%d%d", period.toString(), matcherReward.toString(), emissionReward.toString()], SEP) - -# rewards from matcher and from emission for given period -func keyPayoutHistoryInfo(period: Int) = - makeString(["%s%s%d__payouts__history", period.toString()], SEP) - -# sum of users weights for period -func keyTotalWeightForPeriod(period: Int) = - makeString(["%s%d__totalWeightForPeriod", period.toString()], SEP) - -# user's K value for period -func keyUserKValueForPeriod(period: Int, userIndex: Int) = - makeString(["%s%d%s%d__paramByPeriod", userIndex.toString(), "k", period.toString()], SEP) - -# user's B value for period -func keyUserBValueForPeriod(period: Int, userIndex: Int) = - makeString(["%s%d%s%d__paramByPeriod", userIndex.toString(), "b", period.toString()], SEP) - -func keyUserWeightForPeriod(period: Int, userIndex: Int) = - makeString(["%s%d%s%d__paramByPeriod", userIndex.toString(), "weight", period.toString()], SEP) - func keyReferralsContractAddress() = ["%s%s", "config", "referralsContractAddress"].makeString(SEP) let referralsContractAddressOrFail = keyReferralsContractAddress().getStringOrFail().addressFromStringValue() @@ -256,50 +201,12 @@ func mustManager(i: Invocation) = { } } -# user's weight = k * height + b, scaled by 10^8 -func calcUserWeight(boostingContractAddress: Address, heightForPeriod: Int, period: Int, userIndex: Int) = { - let kLast = keyLastProcessedPeriodOfUser(userIndex) - let kKey = keyUserKValueForPeriod(period, userIndex) - let kRaw = getInteger(boostingContractAddress, kKey) - let kUserWeight = period.keyUserWeightForPeriod(userIndex) - if (kRaw.isDefined()) then { - let k = kRaw.value() - let b = getInteger(boostingContractAddress, keyUserBValueForPeriod(period, userIndex)).value() - let w = k * heightForPeriod + b - if (w > 0) then (w / SCALE, [IntegerEntry(kLast, period), IntegerEntry(kUserWeight, w)]) else (0, []) - } else { # use last saved period - let p = getInteger(this, kLast) - if (p.isDefined() && p.value() <= period) then { - let pv = p.value() - let k = getInteger(boostingContractAddress, keyUserKValueForPeriod(pv, userIndex)).value() - let b = getInteger(boostingContractAddress, keyUserBValueForPeriod(pv, userIndex)).value() - let w = k * heightForPeriod + b - if (w > 0) then (w / SCALE, [IntegerEntry(kUserWeight, w)]) else (0, []) - } else { - (0, []) - } - } -} - -func calcUserWeightForClaim(boostingContractAddress: Address, heightForPeriod: Int, period: Int, userIndex: Int) = { - let kUserWeight = period.keyUserWeightForPeriod(userIndex) - let userWeightOrUnit = kUserWeight.getInteger() - match userWeightOrUnit { - case _: Unit => 0 - case w: Int => w / SCALE - } -} - func getUserIndexByAddress(boostingContractAddressStr: String, userAddress: String) = { let key = makeString(["%s%s%s", "mapping", "user2num", userAddress], SEP) parseIntValue(getString(Address(boostingContractAddressStr.fromBase58String()), key) .valueOrErrorMessage("User address " + userAddress + " is not found in boosting contract data, key=" + key)) } -# period index to place incoming payment next time -func nextPeriod() = - getNumberByKey(keyNextPeriod()) - func commonClaimReward(userAddress: String) = { let cfgArray = readConfigArrayOrFail() let userIdx = getUserIndexByAddress(cfgArray[IdxCfgBoostingContract], userAddress) # will throw if no such user @@ -316,6 +223,81 @@ func getTradingReward(userAddress: String) = { this.getInteger(userAddress.keyTradingReward()).valueOrElse(0) } +func keyRewardPerGwxIntegral() = ["%s", "rewardPerGwxIntegral"].makeString(SEP) + +# call when reward or total gwx amount are changed +func _refreshRewardPerGwxIntegral() = { + # rewardPerGwx = dh * emissionPerBlock / totalGwxAmount + rewardPerGwxPrevious + let rewardPerGwxIntegralPrevious = { + match this.getString(keyRewardPerGwxIntegral()) { + case s: String => s.parseBigInt() + case _: Unit => unit + } + }.valueOrElse(zeroBigInt) + let rewardPerGwxIntegralLastHeight = this.getInteger(keyGwxRewardEmissionStartHeight()) + .valueOrErrorMessage(wrapErr("invalid " + keyGwxRewardEmissionStartHeight())) + let emissionRate = emissionContract.getInteger(keyRatePerBlockCurrent()) + .valueOrErrorMessage(wrapErr("invalid " + keyRatePerBlockCurrent())) + let gwxHoldersRewardCurrent = emissionContract.getInteger(keyGwxHoldersRewardCurrent()) + .valueOrElse(0) + let gwxAmountTotal = boostingContractOrFail().invoke("getGwxTotalREADONLY", [], []).exactAs[Int] + let dh = { height - rewardPerGwxIntegralLastHeight }.toBigInt() + let rewardPerGwxIntegral = rewardPerGwxIntegralPrevious + fraction( + dh, + emissionRate.toBigInt() * gwxHoldersRewardCurrent.toBigInt() * MULT18BI, + gwxAmountTotal.toBigInt() + ) + + ( + [ + IntegerEntry(keyGwxRewardEmissionStartHeight(), height), + StringEntry(keyRewardPerGwxIntegral(), rewardPerGwxIntegral.toString()) + ], + rewardPerGwxIntegral + ) +} + +func keyRewardPerGwxIntegralUserLast(userAddress: Address) = ["%s%s", "rewardPerGwxIntegralUserLast", userAddress.toString()].makeString(SEP) + +# call when user total gwx amount is changed +func _refreshUserReward(userAddress: Address) = { + let rewardPerGwxIntegralUserLast = { + match this.getString(keyRewardPerGwxIntegralUserLast(userAddress)) { + case s: String => s.parseBigInt() + case _: Unit => unit + } + }.valueOrElse(zeroBigInt) + + let userIdxOrFail = getUserIndexByAddress( + boostingContractOrFail().toString(), + userAddress.toString() + ) + let userUnclaimed = userIdxOrFail.keyUserUnclaimed().getInteger().valueOrElse(0) + + let (rewardPerGwxIntegralActions, rewardPerGwxIntegral) = _refreshRewardPerGwxIntegral() + + # userReward = userGwxAmount * (rewardPerGwxIntegral - rewardPerGwxIntegralUserLast) + userRewardPrevious - userRewardClaimed + let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddress.toString()], []).exactAs[Int] + let userReward = fraction(userGwxAmount.toBigInt(), rewardPerGwxIntegral - rewardPerGwxIntegralUserLast, MULT18BI).toInt() + userUnclaimed + + ( + [ + StringEntry( + keyRewardPerGwxIntegralUserLast(userAddress), + rewardPerGwxIntegral.toString() + ) + ] ++ rewardPerGwxIntegralActions, + userReward + ) +} + +@Callable(i) +func refreshUserReward(userAddressBytes: ByteVector) = { + strict checkCaller = i.caller == boostingContractOrFail() || throwErr("permission denied") + + _refreshUserReward(Address(userAddressBytes)) +} + @Callable(i) func tradeRewardInternal( paymentAmountLeftOver: Int, @@ -364,126 +346,9 @@ func updateReferralActivity(userAddress: String, gWxAmountStart: Int) = { (nil, unit) } -@Callable(i) -func finalizeHelper() = { - let processingStage = keyProcessingStage().getInteger().valueOrElse(processingStageTotal) - let currentPeriod = keyNextProcessedPeriod().getNumberByKey() # period with reward being currently distributed - let currentUser = keyNextProcessedUser().getNumberByKey() # user to start with - let latestPeriod = keyLatestPeriod().getNumberByKey() - let usersCount = boostingContractOrFail().getInteger(keyUsersCount()).valueOrElse(0) - let totalWeightKey = currentPeriod.keyTotalWeightForPeriod() - let totalWeight = currentPeriod.keyTotalWeightForPeriod().getNumberByKey() - let heightForPeriod = currentPeriod.keyHeightForPeriod().getNumberByKey() - if (currentPeriod > latestPeriod) then { - # nothing to process - ([], false) - } else if (processingStage == processingStageTotal) then { - # calculate total weight for period - # process one user - let (userWeight, userActions) = boostingContractOrFail().calcUserWeight(heightForPeriod, currentPeriod, currentUser) - let totalWeightNew = totalWeight + userWeight - # if no users left, change processing stage to shares - let processingActions = if (currentUser < usersCount - 1) then { - [IntegerEntry(keyNextProcessedUser(), currentUser + 1)] - } else { - [ - IntegerEntry(keyProcessingStage(), processingStageShares), - IntegerEntry(keyNextProcessedUser(), 0) - ] - } - ( - [ - IntegerEntry(totalWeightKey, totalWeightNew) - ] ++ processingActions ++ userActions, - true - ) - } else if (processingStage == processingStageShares) then { - # calculate user shares for period - let userWeight = boostingContractOrFail().calcUserWeightForClaim(heightForPeriod, currentPeriod, currentUser) - let userAmountMatcherForPeriod = currentPeriod.keyTotalAmountForPeriod().getNumberByKey().fraction(userWeight, totalWeight) - let userAmountEmissionForPeriod = currentPeriod.keyAuxEmissionRewardForPeriod().getNumberByKey().fraction(userWeight, totalWeight) - let userTotalAmount = userAmountEmissionForPeriod + userAmountMatcherForPeriod - let userUnclaimedOption = currentUser.keyUserUnclaimed().getInteger() - let userAddress = boostingContractOrFail().getStringValue(currentUser.keyNumToUserMapping()) - let referrer = referralsContractAddressOrFail.getString(userAddress.keyReferrer()) - strict activeReferralInv = if (referrer == unit) then unit else { - referralsContractAddressOrFail.invoke("updateReferralActivity", [referralProgramName, userAddress, userWeight >= referralMinGWxAmount], []) - } - strict referralInv = if (referrer == unit || userWeight < referralMinGWxAmount) then unit else { - let referrerReward = userTotalAmount.fraction(referrerRewardPermille, SCALE) - let referralReward = userTotalAmount.fraction(referralRewardPermille, SCALE) - referralsContractAddressOrFail.invoke("incUnclaimed", [referralProgramName, userAddress, referrerReward, referralReward], []) - } - let unclaimedActions = [IntegerEntry(currentUser.keyUserUnclaimed(), userUnclaimedOption.valueOrElse(0) + userTotalAmount)] - let processingActions = if (currentUser < usersCount - 1) then { - [IntegerEntry(keyNextProcessedUser(), currentUser + 1)] - } else { - [ - IntegerEntry(keyNextProcessedPeriod(), currentPeriod + 1), - IntegerEntry(keyNextProcessedUser(), 0), - DeleteEntry(keyProcessingStage()) - ] - } - ( - unclaimedActions ++ processingActions, - true - ) - } else "invalid processing stage".throw() -} - -@Callable(i) -func finalizeWrapper(counter: Int) = { - strict result = this.invoke("finalizeHelper", [], []).exactAs[Boolean] - if (!result) then { - if (counter == maxDepth) then "Nothing to process".throw() else ([], unit) - } else { - if (counter > 0) then { - ([], this.invoke("finalizeWrapper", [counter - 1], [])) - } else { - ([], unit) - } - } -} - @Callable(i) func processPendingPeriodsAndUsers() = { - ([], this.invoke("finalizeWrapper", [maxDepth], [])) -} - -# Deposit total WX reward for next period. Also requests 5% emission reward from emission contract -# This total reward should be distributed -# between all the users according to their gWX shares (weights) -# Called by matcher pacemaker -@Callable(i) -func deposit() = { - strict checkCaller = i.caller == votingEmissionContract || i.mustManager() - let period = nextPeriod() - let deltaH = height - keyGwxRewardEmissionStartHeight().getNumberOrFail() - let emissionRate = emissionContract.getInteger(keyRatePerBlockCurrent()) - .valueOrErrorMessage("mandatory emission_contract." + keyRatePerBlockCurrent() + " is not defined") - strict gwxHoldersRewardCurrent = emissionContract.getInteger(keyGwxHoldersRewardCurrent()) - .valueOrElse(0) - let auxAmount = fraction(deltaH * gwxHoldersRewardCurrent, emissionRate, MULT8) - strict em = if (auxAmount > 0) then emissionContract.invoke("emit", [auxAmount], []) else unit # request auxAmount WX from emission contract - - let matcherPart = 0 - let payoutInfo = PeriodPayoutInfo(period, matcherPart, auxAmount) - - strict gwxHoldersRewardUpdated = emissionContract.invoke("gwxHoldersRewardUpdate", [], []).exactAs[Boolean] - - let totalReward = matcherPart + auxAmount - let actions = if (totalReward == 0 && !gwxHoldersRewardUpdated) then [] else [ - IntegerEntry(keyLatestPeriod(), period), - IntegerEntry(keyHeightForPeriod(period), height), # save period start height - IntegerEntry(keyAuxEmissionRewardForPeriod(period), auxAmount), # amount to distribute between users (emission part) - IntegerEntry(keyGwxRewardEmissionStartHeight(), height), # update emission height (for deltaH calculation) - IntegerEntry(keyTotalAmountForPeriod(period), matcherPart), # amount to distribute between users (matcher part) - IntegerEntry(keyNextPeriod(), period + 1), # finalize period (increment period to be processed next time) - StringEntry(keyLastPayoutInfo(), payoutInfo), - StringEntry(keyPayoutHistoryInfo(period), payoutInfo) - ] - - (actions, unit) + (nil, throwErr("deprecated")) } # Send all WX earned to caller @@ -491,17 +356,31 @@ func deposit() = { @Callable(i) func claimReward() = { let cfgArray = readConfigArrayOrFail() - let address = i.caller.toString() - let (amount, actions) = commonClaimReward(address) - strict checkAmount = amount > 0 || "Nothing to claim".throw() - # remove if unused - let amountFromEmission = 0 - let claimedReferral = referralsContractAddressOrFail.invoke("claim", [referralProgramName], []).exactAs[Int] + let userAddress = i.caller + let userAddressStr = userAddress.toString() + let (amount, actions) = commonClaimReward(userAddressStr) + strict checkAmount = amount > 0 || "nothing to claim".throw() + + let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddressStr], []).exactAs[Int] + let referrer = referralsContractAddressOrFail.getString(userAddressStr.keyReferrer()) + strict activeReferralInv = if (referrer == unit) then unit else { + referralsContractAddressOrFail.invoke("updateReferralActivity", [referralProgramName, userAddress, userGwxAmount >= referralMinGWxAmount], []) + } + strict referralInv = if (referrer == unit || userGwxAmount < referralMinGWxAmount) then unit else { + let referrerReward = amount.fraction(referrerRewardPermille, SCALE) + let referralReward = amount.fraction(referralRewardPermille, SCALE) + referralsContractAddressOrFail.invoke("incUnclaimed", [referralProgramName, userAddress, referrerReward, referralReward], []) + } + + strict claimedReferral = referralsContractAddressOrFail.invoke("claim", [referralProgramName], []).exactAs[Int] let totalAmount = amount + claimedReferral - ([ - ScriptTransfer(i.caller, totalAmount, cfgArray[IdxCfgAssetId].fromBase58String()), - HistoryEntry("claim", address, amount, i) - ] ++ actions, [totalAmount, amountFromEmission]) + ( + [ + ScriptTransfer(i.caller, amount, cfgArray[IdxCfgAssetId].fromBase58String()), + HistoryEntry("claim", userAddressStr, totalAmount, i) + ] ++ actions, + totalAmount + ) } # returns total claimable reward by user address @@ -514,36 +393,6 @@ func claimRewardREADONLY(address: String) = { ([], totalAmount) } -# returns -1 if there were no payments via deposit() call -@Callable(i) -func latestFinalizedPeriodREADONLY(address: String) = { - ([], getInteger(this, keyLatestPeriod()).valueOrElse(-1)) -} - -# returns %d%d%d__${latestFinalizedPeriod}__${matcherPart}__${emissionPart} -@Callable(i) -func latestFinalizedPeriodInfoREADONLY(address: String) = { - ([], getStringByKey(keyLastPayoutInfo())) -} - -# *********************** -# GWX MATH -# *********************** -@Callable(i) -func calcGwxParamsREADONLY(gwxAmountStart: Int, lockStartHeight: Int, lockDurationBlocks: Int) = { - let lockEndHeight = lockStartHeight + lockDurationBlocks - let scale8ParamK = -fraction(gwxAmountStart, SCALE, lockDurationBlocks) - let scale8ParamB = fraction(gwxAmountStart, SCALE, lockDurationBlocks) * lockEndHeight - ([], [scale8ParamK, scale8ParamB, nextPeriod()]) -} - -@Callable(i) -func calcGwxAmountStartREADONLY(wxLockAmount: Int, lockDuration: Int, maxLockDuration: Int) = { - let coeffX8 = fraction(lockDuration, MULT8, maxLockDuration) - let gWxAmountStart = fraction(wxLockAmount, coeffX8, MULT8) - ([], [gWxAmountStart]) -} - # save starting height of reward from emission 5% @Callable(i) func onEmissionForGwxStart() = { @@ -551,66 +400,6 @@ func onEmissionForGwxStart() = { [IntegerEntry(keyGwxRewardEmissionStartHeight(), height)] } -@Callable(i) -func latestPeriodEmissionRewardsREADONLY(address: String) = { - let period = nextPeriod() - ([], [getNumberByKey(keyAuxEmissionRewardForPeriod(period))]) -} - -# LP Math - -# D invariant calculation iteratively for 2 tokens -# -# A * sum(x_i) * n^n + D = A * D * n^n + D^(n+1) / (n^n * prod(x_i)) -# -# Converging solution: -# D[j+1] = (A * n^n * sum(x_i) - D[j]^(n+1) / (n^n prod(x_i))) / (A * n^n - 1) -@Callable(i) -func calcD( - x1BigIntStr: String, - x2BigIntStr: String, - ampBigIntStr: String, - aPrecisionBigIntStr: String, - targetPrecisionBigIntStr: String -) = { - let nCoins = 2.toBigInt() - let aPrecision = aPrecisionBigIntStr.parseBigIntValue() - let targetPrecision = targetPrecisionBigIntStr.parseBigIntValue() - let x1 = x1BigIntStr.parseBigIntValue() - let x2 = x2BigIntStr.parseBigIntValue() - let amp = ampBigIntStr.parseBigIntValue() * aPrecision - let s = x1 + x2 - if (s == zeroBigInt) then { - ([], zeroBigInt.toString()) - } else { - let ann = amp * nCoins - let arr = [0, 1, 2, 3, 4, 5, 6] - func calc(acc: (BigInt, BigInt|Unit, Int|Unit), cur: Int) = { - let (d, dPrev, found) = acc - if (found != unit) then acc else { - # dp0 = d - # dp1 = dp0 * d / (x1 * nCoins) - # dp2 = dp1 * d / (x2 * nCoins) = (dp0 * d / (x1 * nCoins)) * d / (x2 * nCoins) = d^3 / (x1 * x2 * nCoins^2) - let dp = d * d * d / (x1 * x2 * nCoins * nCoins) - let dNext = (ann * s / aPrecision + dp * nCoins) * d / ((ann - aPrecision) * d / aPrecision + (nCoins + 1.toBigInt()) * dp) - let dDiff = absBigInt(dNext - d.value()) - if (dDiff <= targetPrecision) then { - (dNext, d, cur) - } else { - (dNext, d, unit) - } - } - } - let (dNext, dPrev, found) = FOLD<7>(arr, (s, unit, unit), calc) - if (found != unit) then { - ([], dNext.toString()) - } else { - let dDiff = { dNext - dPrev.value() }.absBigInt() - { "D calculation error, dDiff = " + dDiff.toString() }.throw() - } - } -} - @Callable(i) func tradeReward(userAddresses: List[String], rewards: List[Int]) = { let argsComparison = userAddresses.size() == rewards.size() diff --git a/ride/gwx_reward_v2.ride b/ride/gwx_reward_v2.ride deleted file mode 100644 index d666127df..000000000 --- a/ride/gwx_reward_v2.ride +++ /dev/null @@ -1,456 +0,0 @@ -{-# STDLIB_VERSION 6 #-} -{-# CONTENT_TYPE DAPP #-} -{-# SCRIPT_TYPE ACCOUNT #-} - -# Required state entries: -# * "%s__config": String ("%s%s%s______") -# * "%s%s__config__factoryAddress": String -# * "%s%s__config__emissionAddress": String -# * "%s%s__config__referralsContractAddress": String -# * "%s__latestPeriod": Integer - -# TODO: seems like we can use keyGwxRewardEmissionStartHeight for integral start height - -let SEP = "__" -let SCALE = 1000 -let MULT8 = 1_0000_0000 -let zeroBigInt = 0.toBigInt() -let processingStageTotal = 0 -let processingStageShares = 1 -let MULT18 = 1_000_000_000_000_000_000 -let MULT18BI = MULT18.toBigInt() - -let wavesString = "WAVES" - -func getNumberByKey(key: String) = this.getInteger(key).valueOrElse(0) -func getNumberOrFail(key: String) = this.getInteger(key).valueOrErrorMessage("mandatory this." + key + " is not defined") -func getStringByKey(key: String) = this.getString(key).valueOrElse("") -func getStringOrFail(key: String) = this.getString(key).valueOrErrorMessage("mandatory this." + key + " is not defined") - -func parseAssetId(input: String) = { - if (input == wavesString) then unit else input.fromBase58String() -} - -func wrapErr(msg: String) = ["gwx_reward.ride:", msg].makeString(" ") -func throwErr(msg: String) = msg.wrapErr().throw() - -func abs(val: Int) = if (val < 0) then -val else val -func absBigInt(val: BigInt) = if (val < zeroBigInt) then -val else val - -let keyMaxDepth = "%s__maxDepth" -let maxDepthDefault = 30 -let maxDepth = this.getInteger(keyMaxDepth).valueOrElse(maxDepthDefault) - -# FACTORY API -# own factory address key -func keyFactoryAddress() = "%s%s__config__factoryAddress" - -# GLOBAL VARIABLES -let factoryAddressStr = getStringOrFail(keyFactoryAddress()) -let factoryContract = factoryAddressStr.addressFromStringValue() - -# EMISSION API -# own emission address key -func keyEmissionAddress() = "%s%s__config__emissionAddress" - -func keyVotingEmissionContract() = ["%s", "votingEmissionContract"].makeString(SEP) -let votingEmissionContract = factoryContract.getStringValue(keyVotingEmissionContract()).addressFromStringValue() - -# Boosting -func keyNumToUserMapping(num: Int) = ["%s%s%s", "mapping", "num2user", num.toString()].makeString(SEP) - -# Referrals -let keyReferralProgramName = ["%s%s", "referral", "programName"].makeString(SEP) -let referralProgramNameDefault = "wxlock" -let referralProgramName = this.getString(keyReferralProgramName).valueOrElse(referralProgramNameDefault) - -let keyReferralMinGWxAmount = ["%s%s", "referral", "minGWxAmount"].makeString(SEP) -let referralMinGWxAmountDefault = 500 * MULT8 -let referralMinGWxAmount = this.getInteger(keyReferralMinGWxAmount).valueOrElse(referralMinGWxAmountDefault) - -let keyReferrerRewardPermille = ["%s%s", "referral", "referrerRewardPermille"].makeString(SEP) -# 50‰ = 5% -let referrerRewardPermilleDefault = 50 -let referrerRewardPermille = this.getInteger(keyReferrerRewardPermille).valueOrElse(referrerRewardPermilleDefault) - -let keyReferralRewardPermille = ["%s%s", "referral", "referralRewardPermille"].makeString(SEP) -# 50‰ = 5% -let referralRewardPermilleDefault = 50 -let referralRewardPermille = this.getInteger(keyReferralRewardPermille).valueOrElse(referralRewardPermilleDefault) - -func keyReferrer(referralAddress: String) = ["%s%s%s", "referrer", referralProgramName, referralAddress].makeString(SEP) - -func keyUnclaimedReferral( - programName: String, - claimerAddress: String -) = makeString(["%s%s%s", "unclaimedReferral", programName, claimerAddress], SEP) - -# GLOBAL VARIABLES -# CONSTRUCTOR IS NOT FAILED BECAUSE GLOBAL VARIABLES ARE NOT USED -let emissionAddressStr = getStringOrFail(keyEmissionAddress()) -let emissionContract = emissionAddressStr.addressFromStringValue() - - -# *********************** -# Config -# *********************** -# index 0 corresponds %s%s%s metadata -let IdxCfgAssetId = 1 -let IdxCfgPacemakerAddress = 2 -let IdxCfgBoostingContract = 3 -let IdxCfgMaxDepth = 4 - -func keyConfig() = "%s__config" - -func getEmissionAddress() = this.getString( - keyEmissionAddress() -).valueOrErrorMessage( - "mandatory this." + keyEmissionAddress() + " is not defined" -).addressFromStringValue() - - -let emissionAddress = getEmissionAddress() -let wxAssetIdStr = emissionAddress.getString(keyConfig()).value().split(SEP)[1] -let wxAssetId = wxAssetIdStr.fromBase58String() - -func readConfigArrayOrFail() = getStringOrFail(keyConfig()).split(SEP) - -func formatConfig(wxAssetIdStr: String, matcherPacemakerAddressStr: String, boostingContractAddressStr: String, maxDepth: Int) = { - makeString([ - "%s%s%s%d", - wxAssetIdStr, # 1 - matcherPacemakerAddressStr, # 2 - boostingContractAddressStr, # 3 - maxDepth.toString() # 4 - ], SEP) -} - -func boostingContractOrFail() = { - let cfgArray = readConfigArrayOrFail() - cfgArray[IdxCfgBoostingContract].addressFromString().valueOrErrorMessage("boosting contract address is not defined") -} - -# *********************** -# KEYS -# *********************** - -func keyGwxRewardEmissionStartHeight() = "%s%s__gwxRewardEmissionPart__startHeight" - -# boosting contract state key, increments every lock() of unique user -func keyUsersCount() = "%s__nextUserNum" - -# emission contract key -func keyRatePerBlockCurrent() = "%s%s__ratePerBlock__current" -func keyGwxHoldersRewardCurrent() = "%s%s__gwxHoldersReward__current" -func keyGwxHoldersRewardNext() = "%s%s__gwxHoldersReward__next" - -# factory contract key -func keyPoolWeightVirtual() = "%s%s__poolWeight__GWXvirtualPOOL" - -func keyUserUnclaimed(userIndex: Int) = - ["%s%d", "userUnclaimed", userIndex.toString()].makeString(SEP) - -func keyReferralsContractAddress() = ["%s%s", "config", "referralsContractAddress"].makeString(SEP) -let referralsContractAddressOrFail = keyReferralsContractAddress().getStringOrFail().addressFromStringValue() - -func keyTradingRewardHistory(user: String, i: Invocation) = { - makeString(["%s%s%s%s", "tradingReward", "history", user, i.transactionId.toBase58String()], SEP) -} - -func keyTradingReward(userAddress: String) = { - makeString(["%s%s", "tradingReward", userAddress], SEP) -} - -func keyMaxRecipients() = ["%s", "maxRecipients"].makeString(SEP) - -func HistoryEntry(type: String, user: String, amount: Int, i: Invocation) = { - let historyKEY = makeString(["%s%s%s%s__history", type, user, i.transactionId.toBase58String()], SEP) - let historyDATA = makeString([ - "%d%d%d%d%d%d", - lastBlock.height.toString(), - lastBlock.timestamp.toString(), - amount.toString()], - SEP) - StringEntry(historyKEY, historyDATA) -} - -func keyManagerPublicKey() = "%s__managerPublicKey" -func keyManagerVaultAddress() = "%s__managerVaultAddress" - -func getManagerVaultAddressOrThis() = { - match keyManagerVaultAddress().getString() { - case s:String => s.addressFromStringValue() - case _=> this - } -} - -func managerPublicKeyOrUnit() = { - let managerVaultAddress = getManagerVaultAddressOrThis() - match managerVaultAddress.getString(keyManagerPublicKey()) { - case s: String => s.fromBase58String() - case _: Unit => unit - } -} - -func mustManager(i: Invocation) = { - let pd = "Permission denied".throw() - - match managerPublicKeyOrUnit() { - case pk: ByteVector => i.callerPublicKey == pk || pd - case _: Unit => i.caller == this || pd - } -} - -func getUserIndexByAddress(boostingContractAddressStr: String, userAddress: String) = { - let key = makeString(["%s%s%s", "mapping", "user2num", userAddress], SEP) - parseIntValue(getString(Address(boostingContractAddressStr.fromBase58String()), key) - .valueOrErrorMessage("User address " + userAddress + " is not found in boosting contract data, key=" + key)) -} - -func commonClaimReward(userAddress: String) = { - let cfgArray = readConfigArrayOrFail() - let userIdx = getUserIndexByAddress(cfgArray[IdxCfgBoostingContract], userAddress) # will throw if no such user - let userUnclaimedOption = userIdx.keyUserUnclaimed().getInteger() - match userUnclaimedOption { - case _: Unit => (0, []) - case u: Int => (u, [ - IntegerEntry(userIdx.keyUserUnclaimed(), 0) - ]) - } -} - -func getTradingReward(userAddress: String) = { - this.getInteger(userAddress.keyTradingReward()).valueOrElse(0) -} - -func keyRewardPerGwxIntegral() = ["%s", "rewardPerGwxIntegral"].makeString(SEP) - -# call when reward or total gwx amount are changed -func _refreshRewardPerGwxIntegral() = { - # rewardPerGwx = dh * emissionPerBlock / totalGwxAmount + rewardPerGwxPrevious - let rewardPerGwxIntegralPrevious = { - match this.getString(keyRewardPerGwxIntegral()) { - case s: String => s.parseBigInt() - case _: Unit => unit - } - }.valueOrElse(zeroBigInt) - let rewardPerGwxIntegralLastHeight = this.getInteger(keyGwxRewardEmissionStartHeight()) - .valueOrErrorMessage(wrapErr("invalid " + keyGwxRewardEmissionStartHeight())) - let emissionRate = emissionContract.getInteger(keyRatePerBlockCurrent()) - .valueOrErrorMessage(wrapErr("invalid " + keyRatePerBlockCurrent())) - let gwxHoldersRewardCurrent = emissionContract.getInteger(keyGwxHoldersRewardCurrent()) - .valueOrElse(0) - let gwxAmountTotal = boostingContractOrFail().invoke("getGwxTotalREADONLY", [], []).exactAs[Int] - let dh = { height - rewardPerGwxIntegralLastHeight }.toBigInt() - let rewardPerGwxIntegral = rewardPerGwxIntegralPrevious + fraction( - dh, - emissionRate.toBigInt() * gwxHoldersRewardCurrent.toBigInt() * MULT18BI, - gwxAmountTotal.toBigInt() - ) - - ( - [ - IntegerEntry(keyGwxRewardEmissionStartHeight(), height), - StringEntry(keyRewardPerGwxIntegral(), rewardPerGwxIntegral.toString()) - ], - rewardPerGwxIntegral - ) -} - -func keyRewardPerGwxIntegralUserLast(userAddress: Address) = ["%s%s", "rewardPerGwxIntegralUserLast", userAddress.toString()].makeString(SEP) - -# call when user total gwx amount is changed -func _refreshUserReward(userAddress: Address) = { - let rewardPerGwxIntegralUserLast = { - match this.getString(keyRewardPerGwxIntegralUserLast(userAddress)) { - case s: String => s.parseBigInt() - case _: Unit => unit - } - }.valueOrElse(zeroBigInt) - - let userIdxOrFail = getUserIndexByAddress( - boostingContractOrFail().toString(), - userAddress.toString() - ) - let userUnclaimed = userIdxOrFail.keyUserUnclaimed().getInteger().valueOrElse(0) - - let (rewardPerGwxIntegralActions, rewardPerGwxIntegral) = _refreshRewardPerGwxIntegral() - - # userReward = userGwxAmount * (rewardPerGwxIntegral - rewardPerGwxIntegralUserLast) + userRewardPrevious - userRewardClaimed - let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddress.toString()], []).exactAs[Int] - let userReward = fraction(userGwxAmount.toBigInt(), rewardPerGwxIntegral - rewardPerGwxIntegralUserLast, MULT18BI).toInt() + userUnclaimed - - ( - [ - StringEntry( - keyRewardPerGwxIntegralUserLast(userAddress), - rewardPerGwxIntegral.toString() - ) - ] ++ rewardPerGwxIntegralActions, - userReward - ) -} - -@Callable(i) -func refreshUserReward(userAddressBytes: ByteVector) = { - strict checkCaller = i.caller == boostingContractOrFail() || throwErr("permission denied") - - _refreshUserReward(Address(userAddressBytes)) -} - -@Callable(i) -func tradeRewardInternal( - paymentAmountLeftOver: Int, - userAddresses: List[String], - rewards: List[Int], - currentIter: Int -) = { - if (currentIter == userAddresses.size()) then ([]) else - - strict checks = [ - i.caller == this || "Permission denied".throwErr(), - paymentAmountLeftOver >= rewards[currentIter] || "insufficient payment assetId".throwErr() - ] - - strict tradeRewardInternal = this.invoke( - "tradeRewardInternal", - [ - paymentAmountLeftOver - rewards[currentIter], - userAddresses, - rewards, - currentIter+1 - ], - [] - ) - - let tradingRewardHistoryKey = keyTradingRewardHistory( - userAddresses[currentIter], - i - ) - - let userAddress = addressFromStringValue(userAddresses[currentIter]) - - ([ - IntegerEntry(tradingRewardHistoryKey, rewards[currentIter]), - IntegerEntry(userAddresses[currentIter].keyTradingReward(), rewards[currentIter]) - ], tradeRewardInternal) -} - -@Callable(i) -func updateReferralActivity(userAddress: String, gWxAmountStart: Int) = { - let referrer = referralsContractAddressOrFail.getString(userAddress.keyReferrer()) - strict activeReferralInv = if (referrer == unit) then unit else { - referralsContractAddressOrFail.invoke("updateReferralActivity", [referralProgramName, userAddress, gWxAmountStart >= referralMinGWxAmount], []) - } - - (nil, unit) -} - -@Callable(i) -func processPendingPeriodsAndUsers() = { - (nil, throwErr("deprecated")) -} - -# Send all WX earned to caller -# called by user -@Callable(i) -func claimReward() = { - let cfgArray = readConfigArrayOrFail() - let userAddress = i.caller - let userAddressStr = userAddress.toString() - let (amount, actions) = commonClaimReward(userAddressStr) - strict checkAmount = amount > 0 || "nothing to claim".throw() - - let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddressStr], []).exactAs[Int] - let referrer = referralsContractAddressOrFail.getString(userAddressStr.keyReferrer()) - strict activeReferralInv = if (referrer == unit) then unit else { - referralsContractAddressOrFail.invoke("updateReferralActivity", [referralProgramName, userAddress, userGwxAmount >= referralMinGWxAmount], []) - } - strict referralInv = if (referrer == unit || userGwxAmount < referralMinGWxAmount) then unit else { - let referrerReward = amount.fraction(referrerRewardPermille, SCALE) - let referralReward = amount.fraction(referralRewardPermille, SCALE) - referralsContractAddressOrFail.invoke("incUnclaimed", [referralProgramName, userAddress, referrerReward, referralReward], []) - } - - strict claimedReferral = referralsContractAddressOrFail.invoke("claim", [referralProgramName], []).exactAs[Int] - let totalAmount = amount + claimedReferral - ( - [ - ScriptTransfer(i.caller, amount, cfgArray[IdxCfgAssetId].fromBase58String()), - HistoryEntry("claim", userAddressStr, totalAmount, i) - ] ++ actions, - totalAmount - ) -} - -# returns total claimable reward by user address -@Callable(i) -func claimRewardREADONLY(address: String) = { - let (amount, actions) = commonClaimReward(address) - let referralUnclaimed = referralsContractAddressOrFail.getInteger(keyUnclaimedReferral(referralProgramName, address)).valueOrElse(0) - let totalAmount = amount + referralUnclaimed - - ([], totalAmount) -} - -# save starting height of reward from emission 5% -@Callable(i) -func onEmissionForGwxStart() = { - if (i.caller != factoryContract) then throw("permissions denied") else - [IntegerEntry(keyGwxRewardEmissionStartHeight(), height)] -} - -@Callable(i) -func tradeReward(userAddresses: List[String], rewards: List[Int]) = { - let argsComparison = userAddresses.size() == rewards.size() - let maxRecipients = keyMaxRecipients().getInteger().valueOrElse(0) - let payment = i.payments[0] - let paymentAssetId = payment.assetId - let paymentAmount = payment.amount - - strict checks = [ - userAddresses.size() <= maxRecipients || "Too many recipients".throwErr(), - argsComparison || "Arguments size mismatch".throwErr(), - paymentAssetId == wxAssetId || "Wrong asset payment".throwErr() - ] - - strict tradeRewardInternal = this.invoke( - "tradeRewardInternal", - [ - paymentAmount, - userAddresses, - rewards, - 0 - ], - [] - ) - - (nil, tradeRewardInternal) -} - -@Callable(i) -func claimTradingReward() = { - let userAddress = i.caller - let userAddressString = userAddress.toString() - let reward = userAddressString.getTradingReward() - if (reward > 0) then { - ([ - ScriptTransfer(userAddress, reward, wxAssetId), - IntegerEntry(keyTradingReward(userAddressString), 0) - ], reward) - } else "nothing to claim".throwErr() -} - -@Callable(i) -func claimTradingRewardREADONLY(userAddress: String) = { - (nil, userAddress.getTradingReward()) -} - -@Verifier(tx) -func verify() = { - let targetPublicKey = match managerPublicKeyOrUnit() { - case pk: ByteVector => pk - case _: Unit => tx.senderPublicKey - } - sigVerify(tx.bodyBytes, tx.proofs[0], targetPublicKey) -} From 9eb8c773f9b0f392df69047e5ca5ec0b25b6ec44 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:38:46 +0500 Subject: [PATCH 40/86] fix migration --- migrations/wxdefi-435-release/index.mjs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/migrations/wxdefi-435-release/index.mjs b/migrations/wxdefi-435-release/index.mjs index bcbb1e6f7..6feb68594 100644 --- a/migrations/wxdefi-435-release/index.mjs +++ b/migrations/wxdefi-435-release/index.mjs @@ -39,6 +39,9 @@ const keyLockParamsRecordOld = (userAddress) => const keyLockParamsRecord = (userAddress, txId) => `%s%s__lock__${userAddress}__${txId}`; +const keyUserGwxAmountTotal = (userAddress) => + `%s%s__gwxAmountTotal__${userAddress}`; + const lockParamsRecord = ({ amount, start, @@ -58,7 +61,7 @@ const lockParamsRecord = ({ const lockParamsData = await api.addresses.data(boostingAddress, { matches: encodeURIComponent( - `%s%s__lock__.+` + `%s%s__lock__[^_]+` ), }); @@ -86,11 +89,18 @@ for (const { key, value } of lockParamsData) { if (amount <= 0) continue; gwxAmountTotal += parseInt(gwxAmount); - actions.push({ - key: keyLockParamsRecord(userAddress, 'legacy'), - type: 'string', - value: lockParamsRecord({ amount, start, duration, timestamp, gwxAmount, wxClaimed: 0 }), - }); + actions.push( + { + key: keyLockParamsRecord(userAddress, 'legacy'), + type: 'string', + value: lockParamsRecord({ amount, start, duration, timestamp, gwxAmount, wxClaimed: 0 }), + }, + { + key: keyUserGwxAmountTotal(userAddress), + type: 'integer', + value: parseInt(gwxAmount), + }, + ); } const chunkSize = 100; const actionsChunks = Array.from( From 0a142efcb87911d82d29ecb3890953daf9602b9d Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:52:15 +0500 Subject: [PATCH 41/86] fix unlock --- ride/boosting.ride | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 4122a4ac2..a534f2cff 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -157,8 +157,15 @@ let IdxLockLastUpdateTimestamp = 4 let IdxLockGwxAmount = 5 let IdxLockWxClaimed = 6 -func keyLockParamsRecord(userAddress: Address, txId: ByteVector) = makeString(["%s%s%s__lock", userAddress.toString(), txId.toBase58String()], SEP) -func readLockParamsRecordOrFail(userAddress: Address, txId: ByteVector) = this.getStringOrFail(keyLockParamsRecord(userAddress, txId)).split(SEP) +func keyLockParamsRecord(userAddress: Address, txId: ByteVector|Unit) = makeString([ + "%s%s%s__lock", + userAddress.toString(), + match txId { + case b: ByteVector => b.toBase58String() + case _: Unit => "legacy" + } +], SEP) +func readLockParamsRecordOrFail(userAddress: Address, txId: ByteVector|Unit) = this.getStringOrFail(keyLockParamsRecord(userAddress, txId)).split(SEP) func keyUserGwxAmountTotal(userAddress: Address) = makeString(["%s%s__gwxAmountTotal", userAddress.toString()], SEP) @@ -678,7 +685,10 @@ func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { let userAddress = userAddressStr.addressFromString() .valueOrErrorMessage(wrapErr("invalid user address")) let txId = txIdStr.fromBase58String() - let userRecordArray = readLockParamsRecordOrFail(userAddress, txId) + let userRecordArray = readLockParamsRecordOrFail( + userAddress, + if (txIdStr == "") then txId else unit + ) let userAmount = userRecordArray[IdxLockAmount].parseIntValue() let lockStart = userRecordArray[IdxLockStart].parseIntValue() From 678d3ab740ca4cee2e33df05c1a4f282adb20d8a Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:57:08 +0500 Subject: [PATCH 42/86] fix unlock --- ride/boosting.ride | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index a534f2cff..7d1b31ceb 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -681,9 +681,9 @@ func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { } @Callable(i) -func unlock(userAddressStr: String, txIdStr: String, amount: Int) = { - let userAddress = userAddressStr.addressFromString() - .valueOrErrorMessage(wrapErr("invalid user address")) +func unlock(txIdStr: String, amount: Int) = { + let userAddress = i.caller + let userAddressStr = userAddress.toString() let txId = txIdStr.fromBase58String() let userRecordArray = readLockParamsRecordOrFail( userAddress, From 64cc2729a8d8c6e3ee83413bc62814d794bb9f46 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:03:33 +0500 Subject: [PATCH 43/86] fix unlock --- ride/boosting.ride | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 7d1b31ceb..9708bde97 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -687,7 +687,7 @@ func unlock(txIdStr: String, amount: Int) = { let txId = txIdStr.fromBase58String() let userRecordArray = readLockParamsRecordOrFail( userAddress, - if (txIdStr == "") then txId else unit + if (txIdStr == "") then unit else txId ) let userAmount = userRecordArray[IdxLockAmount].parseIntValue() From 5ca0bdc8d2420c2e35137951f8b6e2a99e3503d4 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:11:08 +0500 Subject: [PATCH 44/86] fix migration --- migrations/wxdefi-435-release/index.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/wxdefi-435-release/index.mjs b/migrations/wxdefi-435-release/index.mjs index 6feb68594..6fb98229f 100644 --- a/migrations/wxdefi-435-release/index.mjs +++ b/migrations/wxdefi-435-release/index.mjs @@ -37,7 +37,7 @@ const keyLockParamsRecordOld = (userAddress) => `%s%s__lock__${userAddress}`; const keyLockParamsRecord = (userAddress, txId) => - `%s%s__lock__${userAddress}__${txId}`; + `%s%s%s__lock__${userAddress}__${txId}`; const keyUserGwxAmountTotal = (userAddress) => `%s%s__gwxAmountTotal__${userAddress}`; From 7a60e3ff482d6a26a6a8da3501fec5a7ebffbe6f Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:13:10 +0500 Subject: [PATCH 45/86] reentrantInvoke in unlock --- ride/boosting.ride | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 9708bde97..daedc4fae 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -734,7 +734,7 @@ func unlock(txIdStr: String, amount: Int) = { let gwxAmountTotal = getGwxAmountTotal() let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) - strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes], []) + strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes], []) LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + amount) ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed From ba15a1bd4041c7338d552778ea2625835ac3ea5b Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 8 Aug 2023 18:17:23 +0500 Subject: [PATCH 46/86] fix refreshRewardPerGwxIntegral --- ride/gwx_reward.ride | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index d666127df..3819ecbeb 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -14,6 +14,7 @@ let SEP = "__" let SCALE = 1000 let MULT8 = 1_0000_0000 +let MULT8BI = MULT8.toBigInt() let zeroBigInt = 0.toBigInt() let processingStageTotal = 0 let processingStageShares = 1 @@ -245,7 +246,7 @@ func _refreshRewardPerGwxIntegral() = { let rewardPerGwxIntegral = rewardPerGwxIntegralPrevious + fraction( dh, emissionRate.toBigInt() * gwxHoldersRewardCurrent.toBigInt() * MULT18BI, - gwxAmountTotal.toBigInt() + gwxAmountTotal.toBigInt() * MULT8BI ) ( From 899e4a5157c1358483a299934e7613fb10240c38 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 8 Aug 2023 19:34:00 +0500 Subject: [PATCH 47/86] fix unlock --- ride/boosting.ride | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index daedc4fae..cf3ad8083 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -737,9 +737,9 @@ func unlock(txIdStr: String, amount: Int) = { strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes], []) LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + amount) - ++ StatsEntry(-userAmount, 0, 0, -1) # fixme: -1 only if single lock from user existed - :+ HistoryEntry("unlock", userAddressStr, userAmount, lockStart, lockDuration, gwxBurned, i) - :+ ScriptTransfer(userAddress, userAmount, assetId) + ++ StatsEntry(-amount, 0, 0, 0) + :+ HistoryEntry("unlock", userAddressStr, amount, lockStart, lockDuration, gwxBurned, i) + :+ ScriptTransfer(userAddress, amount, assetId) ++ [ IntegerEntry(keyGwxTotal(), ensurePositive(gwxAmountTotal - gwxBurned, "gwxTotal")), IntegerEntry(keyUserGwxAmountTotal(userAddress), ensurePositive(userGwxAmountTotal - gwxBurned, "userGwxAmountTotal")) From cb90e538e3d48240217c14a8beca9d5e4429d935 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:31:15 +0500 Subject: [PATCH 48/86] fix commonClaimReward --- ride/gwx_reward.ride | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index 3819ecbeb..61d14d2fb 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -208,18 +208,6 @@ func getUserIndexByAddress(boostingContractAddressStr: String, userAddress: Stri .valueOrErrorMessage("User address " + userAddress + " is not found in boosting contract data, key=" + key)) } -func commonClaimReward(userAddress: String) = { - let cfgArray = readConfigArrayOrFail() - let userIdx = getUserIndexByAddress(cfgArray[IdxCfgBoostingContract], userAddress) # will throw if no such user - let userUnclaimedOption = userIdx.keyUserUnclaimed().getInteger() - match userUnclaimedOption { - case _: Unit => (0, []) - case u: Int => (u, [ - IntegerEntry(userIdx.keyUserUnclaimed(), 0) - ]) - } -} - func getTradingReward(userAddress: String) = { this.getInteger(userAddress.keyTradingReward()).valueOrElse(0) } @@ -292,6 +280,21 @@ func _refreshUserReward(userAddress: Address) = { ) } +func commonClaimReward(userAddressStr: String) = { + let userAddress = addressFromString(userAddressStr).valueOrErrorMessage(wrapErr("invalid user address")) + let cfgArray = readConfigArrayOrFail() + let userIdx = getUserIndexByAddress(cfgArray[IdxCfgBoostingContract], userAddressStr) # will throw if no such user + let userUnclaimedOption = userIdx.keyUserUnclaimed().getInteger() + let reward = _refreshUserReward(userAddress)._2 + + ( + reward, + [ + IntegerEntry(userIdx.keyUserUnclaimed(), 0) + ] + ) +} + @Callable(i) func refreshUserReward(userAddressBytes: ByteVector) = { strict checkCaller = i.caller == boostingContractOrFail() || throwErr("permission denied") From 90ff0617b4348e5fb116210b37f4bb37aa0a40a9 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:41:12 +0500 Subject: [PATCH 49/86] fix commonClaimReward --- ride/gwx_reward.ride | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index 61d14d2fb..ba18b9d38 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -285,13 +285,13 @@ func commonClaimReward(userAddressStr: String) = { let cfgArray = readConfigArrayOrFail() let userIdx = getUserIndexByAddress(cfgArray[IdxCfgBoostingContract], userAddressStr) # will throw if no such user let userUnclaimedOption = userIdx.keyUserUnclaimed().getInteger() - let reward = _refreshUserReward(userAddress)._2 + let (actions, reward) = _refreshUserReward(userAddress) ( reward, [ IntegerEntry(userIdx.keyUserUnclaimed(), 0) - ] + ] ++ actions ) } From 04eb5cc1cda5258fc1f7027c9f4a07b5c91bff73 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 9 Aug 2023 17:24:48 +0500 Subject: [PATCH 50/86] reentantInvoke in lockActions --- ride/boosting.ride | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index cf3ad8083..fe396c02c 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -616,7 +616,7 @@ func lockActions(i: Invocation, durationMonths: Int) = { let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) - strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes], []) + strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes], []) let arr = if (userIsExisting) then [] else [ IntegerEntry(nextUserNumKEY, userNum + 1), From 40457b35ec4a921d7b5cc95887557944cd2c53e0 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 9 Aug 2023 17:48:44 +0500 Subject: [PATCH 51/86] fix unlock --- ride/boosting.ride | 50 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index fe396c02c..31314dc07 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -633,6 +633,39 @@ func lockActions(i: Invocation, durationMonths: Int) = { ], gWxAmountStart) } +func getWxWithdrawable(userAddress: Address, txIdOption: ByteVector|Unit) = { + let userRecordArray = readLockParamsRecordOrFail( + userAddress, + txIdOption + ) + + let userAmount = userRecordArray[IdxLockAmount].parseIntValue() + let lockStart = userRecordArray[IdxLockStart].parseIntValue() + let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() + let lockEnd = lockStart + lockDuration + let wxClaimed = userRecordArray[IdxLockWxClaimed].parseIntValue() + let gwxAmount = userRecordArray[IdxLockGwxAmount].parseIntValue() + + let t = (height - lockStart) / blocksInDay + + let exponent = fraction( + t.toBigInt(), + { 8 * blocksInDay }.toBigInt() * MULT18BI, + lockDuration.toBigInt() + ) + let wxWithdrawable = if (height > lockEnd) then { + userAmount - wxClaimed + } else { + fraction( + userAmount.toBigInt(), + MULT18BI - pow(5.toBigInt(), 1, exponent, SCALE18, SCALE18, DOWN), + MULT18BI + ).toInt() + } + + wxWithdrawable +} + @Callable(i) func constructor(factoryAddressStr: String, lockAssetIdStr: String, minLockAmount: Int, minDuration: Int, maxDuration: Int, mathContract: String) = { strict checkCaller = i.mustManager() @@ -699,20 +732,10 @@ func unlock(txIdStr: String, amount: Int) = { let t = (height - lockStart) / blocksInDay - let exponent = fraction( - t.toBigInt(), - { 8 * blocksInDay }.toBigInt() * MULT18BI, - lockDuration.toBigInt() + let wxWithdrawable = getWxWithdrawable( + userAddress, + if (txIdStr == "") then unit else txId ) - let wxWithdrawable = if (height > lockEnd) then { - userAmount - wxClaimed - } else { - fraction( - userAmount.toBigInt(), - MULT18BI - pow(5.toBigInt(), 1, exponent, SCALE18, SCALE18, DOWN), - MULT18BI - ).toInt() - } if (amount > wxWithdrawable) then throwErr("maximum amount to unlock: " + wxWithdrawable.toString()) @@ -750,6 +773,7 @@ func unlock(txIdStr: String, amount: Int) = { func gwxUserInfoREADONLY(userAddressStr: String) = { let userAddress = userAddressStr.addressFromString() .valueOrErrorMessage(wrapErr("invalid user address")) + let gwxAmount = getUserGwxAmountTotal(userAddress) ([], [gwxAmount]) From a8de7ed97e987a14b165c592385e594373e634ad Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 10 Aug 2023 12:56:18 +0500 Subject: [PATCH 52/86] blocks in period variable --- migrations/wxdefi-435-release/index.mjs | 4 +++- ride/boosting.ride | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/migrations/wxdefi-435-release/index.mjs b/migrations/wxdefi-435-release/index.mjs index 6fb98229f..2905f9f2e 100644 --- a/migrations/wxdefi-435-release/index.mjs +++ b/migrations/wxdefi-435-release/index.mjs @@ -20,6 +20,7 @@ const { MIN_LOCK_AMOUNT, MIN_LOCK_DURATION, MAX_LOCK_DURATION, + BLOCKS_IN_PERIOD, } = process.env; const api = create(NODE_URL); @@ -141,12 +142,13 @@ txs.push({ key: '%s__config', type: 'string', value: [ - '%s%d%d%d%s', + '%s%d%d%d%s%d', WX_ASSET_ID, MIN_LOCK_AMOUNT.toString(), MIN_LOCK_DURATION.toString(), MAX_LOCK_DURATION.toString(), gwxRewardAddress, + BLOCKS_IN_PERIOD.toString(), ].join(separator), } ], diff --git a/ride/boosting.ride b/ride/boosting.ride index 31314dc07..9fa0665c2 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -6,7 +6,7 @@ # * "%s%s__config__referralsContractAddress": String # * "%s__nextUserNum": Int # * "%s%s__config__factoryAddress": String -# * "%s__config": String ("%s%d%d%d%s__________") +# * "%s__config": String ("%s%d%d%d%s%d____________") # * "%s__lpStakingPoolsContract": String let SEP = "__" @@ -94,6 +94,7 @@ let IdxCfgMinLockAmount = 2 let IdxCfgMinLockDuration = 3 let IdxCfgMaxLockDuration = 4 let IdxCfgMathContract = 5 +let IdxCfgBlocksInPeriod = 6 func keyConfig() = {"%s__config"} func readConfigArrayOrFail() = this.getStringOrFail(keyConfig()).split(SEP) @@ -103,6 +104,7 @@ let minLockAmount = cfgArray[IdxCfgMinLockAmount].parseInt().valueOrErrorMessage let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseInt().valueOrErrorMessage(wrapErr("invalid min lock duration")) let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseInt().valueOrErrorMessage(wrapErr("invalid max lock duration")) let mathContract = cfgArray[IdxCfgMathContract].addressFromString().valueOrErrorMessage(wrapErr("invalid math contract address")) +let blocksInPeriod = cfgArray[IdxCfgBlocksInPeriod].parseInt().valueOrErrorMessage(wrapErr("invalid blocks in period")) func formatConfigS(assetId: String, minLockAmount: String, minLockDuration: String, maxLockDuration: String, mathContract: String) = { makeString([ @@ -646,11 +648,11 @@ func getWxWithdrawable(userAddress: Address, txIdOption: ByteVector|Unit) = { let wxClaimed = userRecordArray[IdxLockWxClaimed].parseIntValue() let gwxAmount = userRecordArray[IdxLockGwxAmount].parseIntValue() - let t = (height - lockStart) / blocksInDay + let t = (height - lockStart) / blocksInPeriod let exponent = fraction( t.toBigInt(), - { 8 * blocksInDay }.toBigInt() * MULT18BI, + { 8 * blocksInPeriod }.toBigInt() * MULT18BI, lockDuration.toBigInt() ) let wxWithdrawable = if (height > lockEnd) then { @@ -730,7 +732,7 @@ func unlock(txIdStr: String, amount: Int) = { let wxClaimed = userRecordArray[IdxLockWxClaimed].parseIntValue() let gwxAmount = userRecordArray[IdxLockGwxAmount].parseIntValue() - let t = (height - lockStart) / blocksInDay + let t = (height - lockStart) / blocksInPeriod let wxWithdrawable = getWxWithdrawable( userAddress, @@ -743,7 +745,7 @@ func unlock(txIdStr: String, amount: Int) = { let gwxBurned = max([ amount, - fraction(t * blocksInDay, userAmount, maxLockDuration) + fraction(t * blocksInPeriod, userAmount, maxLockDuration) ]) let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") let lockedGwxAmount = getLockedGwxAmount(userAddress) From b7d0c8215b84d56d3930a05d856b01556b0168c8 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 10 Aug 2023 16:51:23 +0500 Subject: [PATCH 53/86] fix unlock --- ride/boosting.ride | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 9fa0665c2..5c6d762a4 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -742,11 +742,11 @@ func unlock(txIdStr: String, amount: Int) = { if (amount > wxWithdrawable) then throwErr("maximum amount to unlock: " + wxWithdrawable.toString()) else + + let coeffX8 = fraction(lockDuration, MULT8, maxLockDuration) + let gWxAmountStart = fraction(userAmount, coeffX8, MULT8) - let gwxBurned = max([ - amount, - fraction(t * blocksInPeriod, userAmount, maxLockDuration) - ]) + let gwxBurned = fraction(t * blocksInPeriod, gWxAmountStart, maxLockDuration) let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") let lockedGwxAmount = getLockedGwxAmount(userAddress) From b919904acf94350c6fdbc64787dccc93b97120ef Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 10 Aug 2023 16:57:44 +0500 Subject: [PATCH 54/86] fix getWxWithdrawable --- ride/boosting.ride | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 5c6d762a4..643003bfa 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -656,14 +656,14 @@ func getWxWithdrawable(userAddress: Address, txIdOption: ByteVector|Unit) = { lockDuration.toBigInt() ) let wxWithdrawable = if (height > lockEnd) then { - userAmount - wxClaimed + userAmount } else { fraction( userAmount.toBigInt(), MULT18BI - pow(5.toBigInt(), 1, exponent, SCALE18, SCALE18, DOWN), MULT18BI ).toInt() - } + } - wxClaimed wxWithdrawable } From 1258336ea5eff0ec5d7741d7ea85c5919341fd49 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 10 Aug 2023 17:20:24 +0500 Subject: [PATCH 55/86] fix unlock compare user total gwx amount and locked gwx amount --- ride/boosting.ride | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 643003bfa..5358c0c6d 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -750,14 +750,15 @@ func unlock(txIdStr: String, amount: Int) = { let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") let lockedGwxAmount = getLockedGwxAmount(userAddress) - if (gwxRemaining < lockedGwxAmount) - then throwErr("locked gwx amount: " + lockedGwxAmount.toString()) - else - if (userAmount <= 0) then throwErr("nothing to unlock") else let gwxAmountTotal = getGwxAmountTotal() let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) + let userGwxAmountTotalNew = ensurePositive(userGwxAmountTotal - gwxBurned, "userGwxAmountTotalNew") + + if (userGwxAmountTotalNew < lockedGwxAmount) + then throwErr("locked gwx amount: " + lockedGwxAmount.toString()) + else strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes], []) @@ -767,7 +768,7 @@ func unlock(txIdStr: String, amount: Int) = { :+ ScriptTransfer(userAddress, amount, assetId) ++ [ IntegerEntry(keyGwxTotal(), ensurePositive(gwxAmountTotal - gwxBurned, "gwxTotal")), - IntegerEntry(keyUserGwxAmountTotal(userAddress), ensurePositive(userGwxAmountTotal - gwxBurned, "userGwxAmountTotal")) + IntegerEntry(keyUserGwxAmountTotal(userAddress), userGwxAmountTotalNew) ] } From 6eab40503a3592f32d4a177d95c773fc603a1974 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 10 Aug 2023 19:04:23 +0500 Subject: [PATCH 56/86] unlock all --- ride/boosting.ride | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 5358c0c6d..acc915524 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -716,7 +716,7 @@ func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { } @Callable(i) -func unlock(txIdStr: String, amount: Int) = { +func unlock(txIdStr: String) = { let userAddress = i.caller let userAddressStr = userAddress.toString() let txId = txIdStr.fromBase58String() @@ -738,15 +738,14 @@ func unlock(txIdStr: String, amount: Int) = { userAddress, if (txIdStr == "") then unit else txId ) - - if (amount > wxWithdrawable) - then throwErr("maximum amount to unlock: " + wxWithdrawable.toString()) - else let coeffX8 = fraction(lockDuration, MULT8, maxLockDuration) let gWxAmountStart = fraction(userAmount, coeffX8, MULT8) - let gwxBurned = fraction(t * blocksInPeriod, gWxAmountStart, maxLockDuration) + let gwxBurned = min([ + fraction(t * blocksInPeriod, gWxAmountStart, maxLockDuration), + gwxAmount + ]) let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") let lockedGwxAmount = getLockedGwxAmount(userAddress) @@ -762,10 +761,10 @@ func unlock(txIdStr: String, amount: Int) = { strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes], []) - LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + amount) - ++ StatsEntry(-amount, 0, 0, 0) - :+ HistoryEntry("unlock", userAddressStr, amount, lockStart, lockDuration, gwxBurned, i) - :+ ScriptTransfer(userAddress, amount, assetId) + LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + wxWithdrawable) + ++ StatsEntry(-wxWithdrawable, 0, 0, 0) + :+ HistoryEntry("unlock", userAddressStr, wxWithdrawable, lockStart, lockDuration, gwxBurned, i) + :+ ScriptTransfer(userAddress, wxWithdrawable, assetId) ++ [ IntegerEntry(keyGwxTotal(), ensurePositive(gwxAmountTotal - gwxBurned, "gwxTotal")), IntegerEntry(keyUserGwxAmountTotal(userAddress), userGwxAmountTotalNew) From 6e6c2e96d3d949940ba5c4fe815d30bca6017959 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 11 Aug 2023 20:03:45 +0500 Subject: [PATCH 57/86] refactor --- ride/boosting.ride | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index acc915524..6aae5cf89 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -17,6 +17,7 @@ let contractFilename = "boosting.ride" let SCALE18 = 18 let MULT18 = 1_000_000_000_000_000_000 let MULT18BI = MULT18.toBigInt() +let durationMonthsAllowed = [1, 3, 6, 12, 24, 48] # 24 * 60 let blocksInDay = 1440 @@ -581,7 +582,6 @@ func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly } func lockActions(i: Invocation, durationMonths: Int) = { - let durationMonthsAllowed = [1, 3, 6, 12, 24, 48] if (!durationMonthsAllowed.containsElement(durationMonths)) then throwErr("invalid duration") else let duration = durationMonths * blocksInMonth let assetIdStr = assetId.toBase58String() @@ -608,8 +608,7 @@ func lockActions(i: Invocation, durationMonths: Int) = { if (duration < minLockDuration) then throwErr("passed duration is less then minLockDuration=" + minLockDuration.toString()) else if (duration > maxLockDuration) then throwErr("passed duration is greater then maxLockDuration=" + maxLockDuration.toString()) else - let coeffX8 = fraction(duration, MULT8, maxLockDuration) - let gWxAmountStart = fraction(pmtAmount, coeffX8, MULT8) + let gWxAmountStart = fraction(pmtAmount, duration, maxLockDuration) let gwxAmountTotal = getGwxAmountTotal() @@ -646,7 +645,6 @@ func getWxWithdrawable(userAddress: Address, txIdOption: ByteVector|Unit) = { let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() let lockEnd = lockStart + lockDuration let wxClaimed = userRecordArray[IdxLockWxClaimed].parseIntValue() - let gwxAmount = userRecordArray[IdxLockGwxAmount].parseIntValue() let t = (height - lockStart) / blocksInPeriod @@ -728,7 +726,6 @@ func unlock(txIdStr: String) = { let userAmount = userRecordArray[IdxLockAmount].parseIntValue() let lockStart = userRecordArray[IdxLockStart].parseIntValue() let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() - let lockEnd = lockStart + lockDuration let wxClaimed = userRecordArray[IdxLockWxClaimed].parseIntValue() let gwxAmount = userRecordArray[IdxLockGwxAmount].parseIntValue() @@ -738,9 +735,8 @@ func unlock(txIdStr: String) = { userAddress, if (txIdStr == "") then unit else txId ) - - let coeffX8 = fraction(lockDuration, MULT8, maxLockDuration) - let gWxAmountStart = fraction(userAmount, coeffX8, MULT8) + + let gWxAmountStart = fraction(userAmount, lockDuration, maxLockDuration) let gwxBurned = min([ fraction(t * blocksInPeriod, gWxAmountStart, maxLockDuration), From fc546758a3a53fca639c04627deab32fbda07311 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:48:06 +0500 Subject: [PATCH 58/86] fix formatLockParamsRecord --- ride/boosting.ride | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 6aae5cf89..be0a6086c 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -180,7 +180,7 @@ func formatLockParamsRecord( wxClaimed: Int ) = { makeString([ - "%d%d%d%d%d%d%d", # 0 + "%d%d%d%d%d%d", # 0 amount.toString(), # 1 start.toString(), # 2 duration.toString(), # 3 From 75794759ad8899dcc87cb9cba20317e71102eb47 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:52:17 +0500 Subject: [PATCH 59/86] remove unused keys --- ride/boosting.ride | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index be0a6086c..a59da1580 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -85,10 +85,6 @@ func keyBoostingV2Integral() = "%s%s__boostingV2__integral" func keyEmissionDurationInBlocks() = "%s%s__emission__duration" func keyEmissionEndBlock() = "%s%s__emission__endBlock" -# GWX REWARD (MATH) API -func keyNextPergetIntOrDefault() = "%s__nextPeriod" -func keyGwxRewardEmissionStartHeight() = "%s%s__gwxRewardEmissionPart__startHeight" - # OWN KEYS let IdxCfgAssetId = 1 let IdxCfgMinLockAmount = 2 From 515a52a42890850cfb2edf860de00b1d15401bb3 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 15 Aug 2023 16:14:36 +0500 Subject: [PATCH 60/86] fix userMaxDurationREADONLY --- ride/boosting.ride | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index a59da1580..0d476d3d8 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -776,7 +776,7 @@ func gwxUserInfoREADONLY(userAddressStr: String) = { # for lp_staking_pools @Callable(i) func userMaxDurationREADONLY(userAddressStr: String) = { - ([], ("increaseLock", maxLockDuration)) + ([], ("lock", maxLockDuration)) } # targetHeight is unused From 4cb21b1fd773e38d7380394173df70c8c7cffa6e Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 15 Aug 2023 16:22:37 +0500 Subject: [PATCH 61/86] move duration check in lockActions --- ride/boosting.ride | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 0d476d3d8..e37b1b320 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -578,7 +578,6 @@ func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly } func lockActions(i: Invocation, durationMonths: Int) = { - if (!durationMonthsAllowed.containsElement(durationMonths)) then throwErr("invalid duration") else let duration = durationMonths * blocksInMonth let assetIdStr = assetId.toBase58String() if (i.payments.size() != 1) then throwErr("invalid payment - exact one payment must be attached") else @@ -586,6 +585,7 @@ func lockActions(i: Invocation, durationMonths: Int) = { let pmtAmount = pmt.amount if (assetId != pmt.assetId.value()) then throwErr("invalid asset is in payment - " + assetIdStr + " is expected") else + if (!durationMonthsAllowed.containsElement(durationMonths)) then throwErr("invalid duration") else let nextUserNumKEY = keyNextUserNum() let userAddress = i.caller From 5cf3e17680d94e109e93afae14f5b0054a8a4afe Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 15 Aug 2023 17:57:16 +0500 Subject: [PATCH 62/86] fix fix getUserInfo test fix division by zero for new user --- ride/boosting.ride | 7 +- ride/gwx_reward.ride | 50 +- test/components/boosting/_hooks.mjs | 10 +- test/components/boosting/contract/gwx.mjs | 8 + .../boosting/gwxUserInfoREADONLY.mjs | 50 +- test/package.json | 2 +- test/pnpm-lock.yaml | 854 +++++++++--------- 7 files changed, 496 insertions(+), 485 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index e37b1b320..08a32235d 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -613,7 +613,7 @@ func lockActions(i: Invocation, durationMonths: Int) = { let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) - strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes], []) + strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes, userNum], []) let arr = if (userIsExisting) then [] else [ IntegerEntry(nextUserNumKEY, userNum + 1), @@ -750,8 +750,9 @@ func unlock(txIdStr: String) = { if (userGwxAmountTotalNew < lockedGwxAmount) then throwErr("locked gwx amount: " + lockedGwxAmount.toString()) else - - strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes], []) + let userNum = getString(keyUser2NumMapping(userAddressStr)) + .valueOrErrorMessage(wrapErr("invalid user number")).parseIntValue() + strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes, userNum], []) LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + wxWithdrawable) ++ StatsEntry(-wxWithdrawable, 0, 0, 0) diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index ba18b9d38..e1b895455 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -23,18 +23,18 @@ let MULT18BI = MULT18.toBigInt() let wavesString = "WAVES" +func wrapErr(msg: String) = ["gwx_reward.ride:", msg].makeString(" ") +func throwErr(msg: String) = msg.wrapErr().throw() + func getNumberByKey(key: String) = this.getInteger(key).valueOrElse(0) -func getNumberOrFail(key: String) = this.getInteger(key).valueOrErrorMessage("mandatory this." + key + " is not defined") +func getNumberOrFail(key: String) = this.getInteger(key).valueOrErrorMessage(wrapErr("mandatory this." + key + " is not defined")) func getStringByKey(key: String) = this.getString(key).valueOrElse("") -func getStringOrFail(key: String) = this.getString(key).valueOrErrorMessage("mandatory this." + key + " is not defined") +func getStringOrFail(key: String) = this.getString(key).valueOrErrorMessage(wrapErr("mandatory this." + key + " is not defined")) func parseAssetId(input: String) = { if (input == wavesString) then unit else input.fromBase58String() } -func wrapErr(msg: String) = ["gwx_reward.ride:", msg].makeString(" ") -func throwErr(msg: String) = msg.wrapErr().throw() - func abs(val: Int) = if (val < 0) then -val else val func absBigInt(val: BigInt) = if (val < zeroBigInt) then -val else val @@ -139,6 +139,7 @@ func keyGwxRewardEmissionStartHeight() = "%s%s__gwxRewardEmissionPart__startHeig # boosting contract state key, increments every lock() of unique user func keyUsersCount() = "%s__nextUserNum" +func keyUser2NumMapping(userAddress: Address) = makeString(["%s%s%s__mapping__user2num", userAddress.toString()], SEP) # emission contract key func keyRatePerBlockCurrent() = "%s%s__ratePerBlock__current" @@ -231,10 +232,11 @@ func _refreshRewardPerGwxIntegral() = { .valueOrElse(0) let gwxAmountTotal = boostingContractOrFail().invoke("getGwxTotalREADONLY", [], []).exactAs[Int] let dh = { height - rewardPerGwxIntegralLastHeight }.toBigInt() - let rewardPerGwxIntegral = rewardPerGwxIntegralPrevious + fraction( + let gwxAmountTotalBI = gwxAmountTotal.toBigInt() + let rewardPerGwxIntegral = rewardPerGwxIntegralPrevious + if (gwxAmountTotalBI == zeroBigInt) then zeroBigInt else fraction( dh, emissionRate.toBigInt() * gwxHoldersRewardCurrent.toBigInt() * MULT18BI, - gwxAmountTotal.toBigInt() * MULT8BI + gwxAmountTotalBI * MULT8BI ) ( @@ -249,21 +251,18 @@ func _refreshRewardPerGwxIntegral() = { func keyRewardPerGwxIntegralUserLast(userAddress: Address) = ["%s%s", "rewardPerGwxIntegralUserLast", userAddress.toString()].makeString(SEP) # call when user total gwx amount is changed -func _refreshUserReward(userAddress: Address) = { +func _refreshUserReward(userAddress: Address, userNum: Int) = { + let (rewardPerGwxIntegralActions, rewardPerGwxIntegral) = _refreshRewardPerGwxIntegral() + let rewardPerGwxIntegralUserLast = { match this.getString(keyRewardPerGwxIntegralUserLast(userAddress)) { - case s: String => s.parseBigInt() - case _: Unit => unit + case s: String => s.parseBigInt().valueOrErrorMessage(wrapErr("invalid user last integral")) + case _: Unit => rewardPerGwxIntegral } - }.valueOrElse(zeroBigInt) - - let userIdxOrFail = getUserIndexByAddress( - boostingContractOrFail().toString(), - userAddress.toString() - ) - let userUnclaimed = userIdxOrFail.keyUserUnclaimed().getInteger().valueOrElse(0) + } - let (rewardPerGwxIntegralActions, rewardPerGwxIntegral) = _refreshRewardPerGwxIntegral() + let userIdxOption = boostingContractOrFail().getString(keyUser2NumMapping(userAddress)) + let userUnclaimed = keyUserUnclaimed(userNum).getInteger().valueOrElse(0) # userReward = userGwxAmount * (rewardPerGwxIntegral - rewardPerGwxIntegralUserLast) + userRewardPrevious - userRewardClaimed let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddress.toString()], []).exactAs[Int] @@ -283,23 +282,26 @@ func _refreshUserReward(userAddress: Address) = { func commonClaimReward(userAddressStr: String) = { let userAddress = addressFromString(userAddressStr).valueOrErrorMessage(wrapErr("invalid user address")) let cfgArray = readConfigArrayOrFail() - let userIdx = getUserIndexByAddress(cfgArray[IdxCfgBoostingContract], userAddressStr) # will throw if no such user - let userUnclaimedOption = userIdx.keyUserUnclaimed().getInteger() - let (actions, reward) = _refreshUserReward(userAddress) + let userNum = getUserIndexByAddress(cfgArray[IdxCfgBoostingContract], userAddressStr) # will throw if no such user + let (actions, reward) = _refreshUserReward(userAddress, userNum) ( reward, [ - IntegerEntry(userIdx.keyUserUnclaimed(), 0) + IntegerEntry(userNum.keyUserUnclaimed(), 0) ] ++ actions ) } @Callable(i) -func refreshUserReward(userAddressBytes: ByteVector) = { +func refreshUserReward(userAddressBytes: ByteVector, userNum: Int) = { strict checkCaller = i.caller == boostingContractOrFail() || throwErr("permission denied") - _refreshUserReward(Address(userAddressBytes)) + let (actions, reward) = _refreshUserReward(Address(userAddressBytes), userNum) + + ([ + IntegerEntry(userNum.keyUserUnclaimed(), reward) + ] ++ actions, reward) } @Callable(i) diff --git a/test/components/boosting/_hooks.mjs b/test/components/boosting/_hooks.mjs index a8092ab36..c9968e7e5 100644 --- a/test/components/boosting/_hooks.mjs +++ b/test/components/boosting/_hooks.mjs @@ -8,7 +8,9 @@ import { import { table, getBorderCharacters } from 'table'; import { format } from 'path'; import { setScriptFromFile } from '../../utils/utils.mjs'; -import { api, broadcastAndWait, waitForHeight } from '../../utils/api.mjs'; +import { + api, broadcastAndWait, waitForHeight, chainId, baseSeed, +} from '../../utils/api.mjs'; import { staking } from './contract/staking.mjs'; import { boosting } from './contract/boosting.mjs'; import { emission } from './contract/emission.mjs'; @@ -18,7 +20,6 @@ import { gwx } from './contract/gwx.mjs'; import { votingEmission } from './contract/votingEmission.mjs'; import { managerVault } from './contract/managerVault.mjs'; -const { CHAIN_ID: chainId, BASE_SEED: baseSeed } = process.env; const nonceLength = 3; const ridePath = '../ride'; @@ -156,6 +157,11 @@ export const mochaHooks = { await gwx.init({ caller: this.accounts.gwx.seed, referralAddress: this.accounts.referral.addr, + wxAssetId: this.wxAssetId, + matcherPacemakerAddress: '', + boostingContractAddress: this.accounts.boosting.addr, + gwxRewardEmissionPartStartHeight: 1, + emissionContractAddress: this.accounts.emission.addr, }); await assetsStore.init({ diff --git a/test/components/boosting/contract/gwx.mjs b/test/components/boosting/contract/gwx.mjs index c99807302..43afffc7e 100644 --- a/test/components/boosting/contract/gwx.mjs +++ b/test/components/boosting/contract/gwx.mjs @@ -5,10 +5,18 @@ export const gwx = { init: async ({ caller, referralAddress, + wxAssetId, + matcherPacemakerAddress, + boostingContractAddress, + gwxRewardEmissionPartStartHeight, + emissionContractAddress, }) => { const dataTx = data({ data: [ + { key: '%s__config', type: 'string', value: `%s%s%s__${wxAssetId}__${matcherPacemakerAddress}__${boostingContractAddress}` }, { key: '%s%s__config__referralsContractAddress', type: 'string', value: referralAddress }, + { key: '%s%s__config__emissionAddress', type: 'string', value: emissionContractAddress }, + { key: '%s%s__gwxRewardEmissionPart__startHeight', type: 'integer', value: gwxRewardEmissionPartStartHeight }, ], additionalFee: 4e5, chainId, diff --git a/test/components/boosting/gwxUserInfoREADONLY.mjs b/test/components/boosting/gwxUserInfoREADONLY.mjs index 1d283850b..e16f550ef 100644 --- a/test/components/boosting/gwxUserInfoREADONLY.mjs +++ b/test/components/boosting/gwxUserInfoREADONLY.mjs @@ -1,30 +1,22 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { - data, transfer, reissue, } from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; -import { broadcastAndWait, waitForHeight } from '../../utils/api.mjs'; +import { + broadcastAndWait, waitForHeight, api, chainId, +} from '../../utils/api.mjs'; import { boosting } from './contract/boosting.mjs'; chai.use(chaiAsPromised); const { expect } = chai; -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - describe('boosting: gwxUserInfoREADONLY.mjs', /** @this {MochaSuiteModified} */() => { it( 'should successfully gwxUserInfoREADONLY', async function () { - const k = 1000; - const b = 10; - const lpAssetAmount = 1e3 * 1e8; const wxAmount = 1e3 * 1e8; @@ -51,44 +43,26 @@ describe('boosting: gwxUserInfoREADONLY.mjs', /** @this {MochaSuiteModified} */( }, this.accounts.factory.seed); await broadcastAndWait(lpAssetTransferTx); + const durationInMonths = 1; const { height: lockStartHeight } = await boosting.lock({ dApp: this.accounts.boosting.addr, caller: this.accounts.user0.seed, - duration: this.maxLockDuration, + duration: durationInMonths, payments: [{ assetId: this.wxAssetId, amount: wxAmount }], }); await waitForHeight(lockStartHeight + 1); - const setKTx = data({ - additionalFee: 4e5, - data: [{ - key: '%s%d%s__paramByUserNum__0__k', - type: 'integer', - value: k, - }], - chainId, - }, this.accounts.boosting.seed); - await broadcastAndWait(setKTx); - - const setBTx = data({ - additionalFee: 4e5, - data: [{ - key: '%s%d%s__paramByUserNum__0__b', - type: 'integer', - value: b, - }], - chainId, - }, this.accounts.boosting.seed); - const { height } = await broadcastAndWait(setBTx); - - const expectedGwxAmount = Math.floor((k * height + b) / 1000); - - const expr = `gwxUserInfoREADONLY(\"${this.accounts.user0.addr}\")`; /* eslint-disable-line */ + const expr = `gwxUserInfoREADONLY("${this.accounts.user0.addr}")`; const response = await api.utils.fetchEvaluate( this.accounts.boosting.addr, expr, ); - const checkData = response.result.value._2.value; /* eslint-disable-line */ + const checkData = response.result.value._2.value; + + const blocksInMonth = 43800; + const expectedGwxAmount = Math.floor(( + wxAmount * durationInMonths * blocksInMonth + ) / this.maxLockDuration); // TODO: check all checkData expect(checkData[0]).to.eql({ diff --git a/test/package.json b/test/package.json index 60e2b75ee..05da4e7d0 100644 --- a/test/package.json +++ b/test/package.json @@ -21,7 +21,7 @@ "test-lp": "mocha -r dotenv/config --parallel --require components/lp/_hooks.mjs components/lp", "test-lp-stable": "mocha -r dotenv/config --parallel --require components/lp_stable/_hooks.mjs components/lp_stable", "test-referral": "node_modules/.bin/mocha --parallel --require components/referral/_hooks.mjs components/referral --timeout 999999", - "test-boosting": "node_modules/.bin/mocha -r dotenv/config --parallel --require components/boosting/_hooks.mjs components/boosting", + "test-boosting": "mocha -r dotenv/config --parallel --require components/boosting/_hooks.mjs components/boosting", "test-otc-multiasset": "node_modules/.bin/mocha --parallel --require components/otc_multiasset/_hooks.mjs components/otc_multiasset", "test-ido": "node_modules/.bin/mocha --parallel --require components/ido/_hooks.mjs components/ido", "test-lp-staking-pools": "mocha -r dotenv/config --parallel --require components/lp_staking_pools/_hooks.mjs components/lp_staking_pools", diff --git a/test/pnpm-lock.yaml b/test/pnpm-lock.yaml index a69fdb635..1e4bf135a 100644 --- a/test/pnpm-lock.yaml +++ b/test/pnpm-lock.yaml @@ -1,70 +1,97 @@ -lockfileVersion: 5.4 - -specifiers: - '@types/chai': ^4.3.0 - '@types/chai-as-promised': ^7.1.5 - '@types/mocha': ^9.1.0 - '@waves/bignumber': ^1.1.1 - '@waves/node-api-js': ^1.3.0 - '@waves/ride-js': ^2.2.0 - '@waves/ts-lib-crypto': ^1.4.4-beta.1 - '@waves/ts-types': ^1.1.0 - '@waves/waves-transactions': ^4.2.5-beta.3 - axios: ^0.26.1 - chai: ^4.3.6 - chai-as-promised: ^7.1.1 - concurrently: ^7.1.0 - dotenv: ^16.0.3 - eslint: ^8.10.0 - eslint-config-airbnb-base: ^15.0.0 - eslint-plugin-import: ^2.25.4 - level: ^8.0.0 - mocha: ^9.2.1 - ora: ^6.1.0 - prompts: ^2.4.2 - table: ^6.8.0 +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false dependencies: - '@types/chai': 4.3.0 - '@types/chai-as-promised': 7.1.5 - '@types/mocha': 9.1.0 - '@waves/bignumber': 1.1.1 - '@waves/node-api-js': 1.3.0 - '@waves/ride-js': 2.2.0 - '@waves/ts-lib-crypto': 1.4.4-beta.1 - '@waves/ts-types': 1.1.0 - '@waves/waves-transactions': 4.2.5-beta.3 - axios: 0.26.1 - chai: 4.3.6 - chai-as-promised: 7.1.1_chai@4.3.6 - concurrently: 7.1.0 - eslint: 8.10.0 - eslint-config-airbnb-base: 15.0.0_rnagsyfcubvpoxo2ynj23pim7u - eslint-plugin-import: 2.25.4_eslint@8.10.0 - level: 8.0.0 - mocha: 9.2.1 - ora: 6.1.0 - prompts: 2.4.2 - table: 6.8.0 + '@types/chai': + specifier: ^4.3.0 + version: 4.3.0 + '@types/chai-as-promised': + specifier: ^7.1.5 + version: 7.1.5 + '@types/mocha': + specifier: ^9.1.0 + version: 9.1.0 + '@waves/bignumber': + specifier: ^1.1.1 + version: 1.1.1 + '@waves/node-api-js': + specifier: ^1.3.0 + version: 1.3.0 + '@waves/ride-js': + specifier: ^2.2.7 + version: 2.2.7 + '@waves/ts-lib-crypto': + specifier: ^1.4.4-beta.1 + version: 1.4.4-beta.1 + '@waves/ts-types': + specifier: ^1.1.0 + version: 1.1.0 + '@waves/waves-transactions': + specifier: ^4.2.5-beta.3 + version: 4.2.5-beta.3 + axios: + specifier: ^0.26.1 + version: 0.26.1 + chai: + specifier: ^4.3.6 + version: 4.3.6 + chai-as-promised: + specifier: ^7.1.1 + version: 7.1.1(chai@4.3.6) + chai-subset: + specifier: ^1.6.0 + version: 1.6.0 + concurrently: + specifier: ^7.1.0 + version: 7.1.0 + eslint: + specifier: ^8.10.0 + version: 8.10.0 + eslint-config-airbnb-base: + specifier: ^15.0.0 + version: 15.0.0(eslint-plugin-import@2.25.4)(eslint@8.10.0) + eslint-plugin-import: + specifier: ^2.25.4 + version: 2.25.4(eslint@8.10.0) + level: + specifier: ^8.0.0 + version: 8.0.0 + mocha: + specifier: ^9.2.1 + version: 9.2.1 + ora: + specifier: ^6.1.0 + version: 6.1.0 + prompts: + specifier: ^2.4.2 + version: 2.4.2 + table: + specifier: ^6.8.0 + version: 6.8.0 devDependencies: - dotenv: 16.0.3 + dotenv: + specifier: ^16.0.3 + version: 16.0.3 packages: - /@babel/code-frame/7.18.6: + /@babel/code-frame@7.18.6: resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.18.6 dev: false - /@babel/helper-validator-identifier/7.18.6: + /@babel/helper-validator-identifier@7.18.6: resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==} engines: {node: '>=6.9.0'} dev: false - /@babel/highlight/7.18.6: + /@babel/highlight@7.18.6: resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} engines: {node: '>=6.9.0'} dependencies: @@ -73,12 +100,12 @@ packages: js-tokens: 4.0.0 dev: false - /@eslint/eslintrc/1.2.0: + /@eslint/eslintrc@1.2.0: resolution: {integrity: sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.3 + debug: 4.3.3(supports-color@8.1.1) espree: 9.3.1 globals: 13.12.1 ignore: 4.0.6 @@ -90,22 +117,22 @@ packages: - supports-color dev: false - /@humanwhocodes/config-array/0.9.5: + /@humanwhocodes/config-array@0.9.5: resolution: {integrity: sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==} engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.3 + debug: 4.3.3(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color dev: false - /@humanwhocodes/object-schema/1.2.1: + /@humanwhocodes/object-schema@1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: false - /@jest/environment/27.5.1: + /@jest/environment@27.5.1: resolution: {integrity: sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: @@ -115,7 +142,7 @@ packages: jest-mock: 27.5.1 dev: false - /@jest/fake-timers/27.5.1: + /@jest/fake-timers@27.5.1: resolution: {integrity: sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: @@ -127,7 +154,7 @@ packages: jest-util: 27.5.1 dev: false - /@jest/globals/27.5.1: + /@jest/globals@27.5.1: resolution: {integrity: sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: @@ -136,7 +163,7 @@ packages: expect: 27.5.1 dev: false - /@jest/types/27.5.1: + /@jest/types@27.5.1: resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: @@ -147,143 +174,143 @@ packages: chalk: 4.1.2 dev: false - /@protobufjs/aspromise/1.1.2: + /@protobufjs/aspromise@1.1.2: resolution: {integrity: sha1-m4sMxmPWaafY9vXQiToU00jzD78=} dev: false - /@protobufjs/base64/1.1.2: + /@protobufjs/base64@1.1.2: resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} dev: false - /@protobufjs/codegen/2.0.4: + /@protobufjs/codegen@2.0.4: resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} dev: false - /@protobufjs/eventemitter/1.1.0: + /@protobufjs/eventemitter@1.1.0: resolution: {integrity: sha1-NVy8mLr61ZePntCV85diHx0Ga3A=} dev: false - /@protobufjs/fetch/1.1.0: + /@protobufjs/fetch@1.1.0: resolution: {integrity: sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=} dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/inquire': 1.1.0 dev: false - /@protobufjs/float/1.0.2: + /@protobufjs/float@1.0.2: resolution: {integrity: sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=} dev: false - /@protobufjs/inquire/1.1.0: + /@protobufjs/inquire@1.1.0: resolution: {integrity: sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=} dev: false - /@protobufjs/path/1.1.2: + /@protobufjs/path@1.1.2: resolution: {integrity: sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=} dev: false - /@protobufjs/pool/1.1.0: + /@protobufjs/pool@1.1.0: resolution: {integrity: sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=} dev: false - /@protobufjs/utf8/1.1.0: + /@protobufjs/utf8@1.1.0: resolution: {integrity: sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=} dev: false - /@sinonjs/commons/1.8.3: + /@sinonjs/commons@1.8.3: resolution: {integrity: sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==} dependencies: type-detect: 4.0.8 dev: false - /@sinonjs/fake-timers/8.1.0: + /@sinonjs/fake-timers@8.1.0: resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==} dependencies: '@sinonjs/commons': 1.8.3 dev: false - /@types/base64-js/1.3.0: + /@types/base64-js@1.3.0: resolution: {integrity: sha512-ZmI0sZGAUNXUfMWboWwi4LcfpoVUYldyN6Oe0oJ5cCsHDU/LlRq8nQKPXhYLOx36QYSW9bNIb1vvRrD6K7Llgw==} dev: false - /@types/chai-as-promised/7.1.5: + /@types/chai-as-promised@7.1.5: resolution: {integrity: sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==} dependencies: '@types/chai': 4.3.0 dev: false - /@types/chai/4.3.0: + /@types/chai@4.3.0: resolution: {integrity: sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==} dev: false - /@types/istanbul-lib-coverage/2.0.4: + /@types/istanbul-lib-coverage@2.0.4: resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} dev: false - /@types/istanbul-lib-report/3.0.0: + /@types/istanbul-lib-report@3.0.0: resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} dependencies: '@types/istanbul-lib-coverage': 2.0.4 dev: false - /@types/istanbul-reports/3.0.1: + /@types/istanbul-reports@3.0.1: resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} dependencies: '@types/istanbul-lib-report': 3.0.0 dev: false - /@types/json5/0.0.29: + /@types/json5@0.0.29: resolution: {integrity: sha1-7ihweulOEdK4J7y+UnC86n8+ce4=} dev: false - /@types/long/4.0.1: + /@types/long@4.0.1: resolution: {integrity: sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==} dev: false - /@types/mocha/9.1.0: + /@types/mocha@9.1.0: resolution: {integrity: sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==} dev: false - /@types/node-fetch/2.6.1: + /@types/node-fetch@2.6.1: resolution: {integrity: sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==} dependencies: '@types/node': 18.0.1 form-data: 3.0.1 dev: false - /@types/node/17.0.21: + /@types/node@17.0.21: resolution: {integrity: sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==} dev: false - /@types/node/18.0.1: + /@types/node@18.0.1: resolution: {integrity: sha512-CmR8+Tsy95hhwtZBKJBs0/FFq4XX7sDZHlGGf+0q+BRZfMbOTkzkj0AFAuTyXbObDIoanaBBW0+KEW+m3N16Wg==} dev: false - /@types/stack-utils/2.0.1: + /@types/stack-utils@2.0.1: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: false - /@types/yargs-parser/21.0.0: + /@types/yargs-parser@21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: false - /@types/yargs/16.0.4: + /@types/yargs@16.0.4: resolution: {integrity: sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==} dependencies: '@types/yargs-parser': 21.0.0 dev: false - /@ungap/promise-all-settled/1.1.2: + /@ungap/promise-all-settled@1.1.2: resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} dev: false - /@waves/bignumber/1.1.1: + /@waves/bignumber@1.1.1: resolution: {integrity: sha512-WUY0R0y0Rd92nbyQHbIFDXCWh2YMtf5FYtpoTv4yRomM75cRLJ0/NIQ828guUXLKeVytKzWgvDYj1CZfxatDkg==} dependencies: bignumber.js: 9.0.2 dev: false - /@waves/marshall/0.14.0: + /@waves/marshall@0.14.0: resolution: {integrity: sha512-zcmDEwlD3dgzaTX6d2UM57KaGO6DK759b9EfdGa48UzwsjLdqX+v/6hrcqZEPUYMeymD6fO8O4/S+0RDxue8Wg==} dependencies: '@types/base64-js': 1.3.0 @@ -293,7 +320,7 @@ packages: long: 4.0.0 dev: false - /@waves/node-api-js/1.3.0: + /@waves/node-api-js@1.3.0: resolution: {integrity: sha512-FEI42KM1C6hE541kexV/eqWDeBrVxeMswZbHQ9kRlFdso/kKmouhhjV73NI/zFCSwMzbFI4YDe2ElOSim0DyEA==} dependencies: '@types/node-fetch': 2.6.1 @@ -305,19 +332,19 @@ packages: - encoding dev: false - /@waves/parse-json-bignumber/1.0.3: + /@waves/parse-json-bignumber@1.0.3: resolution: {integrity: sha512-zBHIQUjjMYMQXNQcwJwzNShUZnoTM6JfVJDwa0eDGUVk+JAKVGiXxv/k29Ng9TsIDi97hwVravlPPwfZcy4XXQ==} dev: false - /@waves/protobuf-serialization/1.4.1-beta.1: + /@waves/protobuf-serialization@1.4.1-beta.1: resolution: {integrity: sha512-IjEwyWmjyesjURvhvB2DK/QZ8mKFuBg7zz5SvgG6q8/ofnC4oplSOsWsgT2DjOG4OzGaMUv7Kkv/ZV1qTQxO9g==} dependencies: '@types/long': 4.0.1 protobufjs: 6.11.2 dev: false - /@waves/ride-js/2.2.0: - resolution: {integrity: sha512-a46cZYAE9Cp2DjXPcqrx4CQRtTGLIr+gIvPomRCkDqZpgtd2DvSh6alLJryYMSeMHlgugmZv/qos5Z7OMAs4eQ==} + /@waves/ride-js@2.2.7: + resolution: {integrity: sha512-mL0BgWVMFnqvoL5d9XYbKJfAE6FXs3FXgrsQpRJj5RiOxtIplFWM2Q+EbiIvSmAZ6EUhP/5ufaIbbZeNkMMOIA==} dependencies: '@jest/globals': 27.5.1 '@waves/ts-lib-crypto': 1.4.3 @@ -326,29 +353,29 @@ packages: - supports-color dev: false - /@waves/ts-lib-crypto/1.4.3: + /@waves/ts-lib-crypto@1.4.3: resolution: {integrity: sha512-2pKgyvtLapgM5vpaUEYzX7NYe2bkB+HdWn9W/4d7UFKwyg6zoOYhRQWyb6GuLi3OLHTETgiqpcMZvciFA0Ds6g==} dependencies: js-sha3: 0.8.0 node-forge: 0.8.5 dev: false - /@waves/ts-lib-crypto/1.4.4-beta.1: + /@waves/ts-lib-crypto@1.4.4-beta.1: resolution: {integrity: sha512-tlvThkMCoCDicOznW82wDZWQqfAWcm6ulQnuNzR++X9o0EOHM3Cj8LlS2pkgF0YjZrqEYHTp/4e0RXXYVY+dpw==} dependencies: js-sha3: 0.8.0 node-forge: 0.10.0 dev: false - /@waves/ts-types/1.0.6-beta.4: + /@waves/ts-types@1.0.6-beta.4: resolution: {integrity: sha512-TyFzgYiWkJ5PF7F3XxAE1dudti5a9MVh2BgtGhYKIrSanXNPXhT9KYYRkQRIemYP+HQ4y89rEBf/CUszTdnaag==} dev: false - /@waves/ts-types/1.1.0: + /@waves/ts-types@1.1.0: resolution: {integrity: sha512-SGHj4cIIvMAhDPiDhbpEzP2UqNF3VgTssGf6UaJ7vwzxq0W1pqz2lKMDe9pZup9p9rEETGW4Yy3+K1G7OGOLxA==} dev: false - /@waves/waves-transactions/4.2.5-beta.3: + /@waves/waves-transactions@4.2.5-beta.3: resolution: {integrity: sha512-LiEya9gV0KAaxEVusKEPiXb6fZYuVOUePUfC9YjasODjGT4KLOFp5Po3u746Rt7T04S/3cUaxk19WhdfbpGBtQ==} dependencies: '@waves/marshall': 0.14.0 @@ -363,7 +390,7 @@ packages: - supports-color dev: false - /abstract-level/1.0.3: + /abstract-level@1.0.3: resolution: {integrity: sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==} engines: {node: '>=12'} dependencies: @@ -376,7 +403,7 @@ packages: queue-microtask: 1.2.3 dev: false - /acorn-jsx/5.3.2_acorn@8.7.0: + /acorn-jsx@5.3.2(acorn@8.7.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -384,13 +411,13 @@ packages: acorn: 8.7.0 dev: false - /acorn/8.7.0: + /acorn@8.7.0: resolution: {integrity: sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==} engines: {node: '>=0.4.0'} hasBin: true dev: false - /ajv/6.12.6: + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: fast-deep-equal: 3.1.3 @@ -399,7 +426,7 @@ packages: uri-js: 4.4.1 dev: false - /ajv/8.11.0: + /ajv@8.11.0: resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} dependencies: fast-deep-equal: 3.1.3 @@ -408,41 +435,41 @@ packages: uri-js: 4.4.1 dev: false - /ansi-colors/4.1.1: + /ansi-colors@4.1.1: resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} engines: {node: '>=6'} dev: false - /ansi-regex/5.0.1: + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} dev: false - /ansi-regex/6.0.1: + /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} dev: false - /ansi-styles/3.2.1: + /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} dependencies: color-convert: 1.9.3 dev: false - /ansi-styles/4.3.0: + /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} dependencies: color-convert: 2.0.1 dev: false - /ansi-styles/5.2.0: + /ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} dev: false - /anymatch/3.1.2: + /anymatch@3.1.2: resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} engines: {node: '>= 8'} dependencies: @@ -450,11 +477,11 @@ packages: picomatch: 2.3.1 dev: false - /argparse/2.0.1: + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: false - /array-includes/3.1.4: + /array-includes@3.1.4: resolution: {integrity: sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==} engines: {node: '>= 0.4'} dependencies: @@ -465,7 +492,7 @@ packages: is-string: 1.0.7 dev: false - /array.prototype.flat/1.2.5: + /array.prototype.flat@1.2.5: resolution: {integrity: sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==} engines: {node: '>= 0.4'} dependencies: @@ -474,20 +501,20 @@ packages: es-abstract: 1.19.1 dev: false - /assertion-error/1.1.0: + /assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} dev: false - /astral-regex/2.0.0: + /astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} dev: false - /asynckit/0.4.0: + /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: false - /axios/0.19.2: + /axios@0.19.2: resolution: {integrity: sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==} deprecated: Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410 dependencies: @@ -496,7 +523,7 @@ packages: - supports-color dev: false - /axios/0.26.1: + /axios@0.26.1: resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} dependencies: follow-redirects: 1.14.9 @@ -504,24 +531,24 @@ packages: - debug dev: false - /balanced-match/1.0.2: + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: false - /base64-js/1.5.1: + /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false - /bignumber.js/9.0.2: + /bignumber.js@9.0.2: resolution: {integrity: sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==} dev: false - /binary-extensions/2.2.0: + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} dev: false - /bl/5.0.0: + /bl@5.0.0: resolution: {integrity: sha512-8vxFNZ0pflFfi0WXA3WQXlj6CaMEwsmh63I1CNp0q+wWv8sD0ARx1KovSQd0l2GkwrMIOyedq0EF1FxI+RCZLQ==} dependencies: buffer: 6.0.3 @@ -529,21 +556,21 @@ packages: readable-stream: 3.6.0 dev: false - /brace-expansion/1.1.11: + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 dev: false - /braces/3.0.2: + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} dependencies: fill-range: 7.0.1 dev: false - /browser-level/1.0.1: + /browser-level@1.0.1: resolution: {integrity: sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==} dependencies: abstract-level: 1.0.3 @@ -552,40 +579,40 @@ packages: run-parallel-limit: 1.1.0 dev: false - /browser-stdout/1.3.1: + /browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} dev: false - /buffer/6.0.3: + /buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 dev: false - /call-bind/1.0.2: + /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: function-bind: 1.1.1 get-intrinsic: 1.1.1 dev: false - /callsites/3.1.0: + /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} dev: false - /camelcase/6.3.0: + /camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} dev: false - /catering/2.1.1: + /catering@2.1.1: resolution: {integrity: sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==} engines: {node: '>=6'} dev: false - /chai-as-promised/7.1.1_chai@4.3.6: + /chai-as-promised@7.1.1(chai@4.3.6): resolution: {integrity: sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==} peerDependencies: chai: '>= 2.1.2 < 5' @@ -594,7 +621,12 @@ packages: check-error: 1.0.2 dev: false - /chai/4.3.6: + /chai-subset@1.6.0: + resolution: {integrity: sha512-K3d+KmqdS5XKW5DWPd5sgNffL3uxdDe+6GdnJh3AYPhwnBGRY5urfvfcbRtWIvvpz+KxkL9FeBB6MZewLUNwug==} + engines: {node: '>=4'} + dev: false + + /chai@4.3.6: resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==} engines: {node: '>=4'} dependencies: @@ -607,7 +639,7 @@ packages: type-detect: 4.0.8 dev: false - /chalk/2.4.2: + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} dependencies: @@ -616,7 +648,7 @@ packages: supports-color: 5.5.0 dev: false - /chalk/4.1.2: + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} dependencies: @@ -624,16 +656,16 @@ packages: supports-color: 7.2.0 dev: false - /chalk/5.0.1: + /chalk@5.0.1: resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: false - /check-error/1.0.2: + /check-error@1.0.2: resolution: {integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=} dev: false - /chokidar/3.5.3: + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} dependencies: @@ -648,11 +680,11 @@ packages: fsevents: 2.3.2 dev: false - /ci-info/3.3.2: + /ci-info@3.3.2: resolution: {integrity: sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==} dev: false - /classic-level/1.2.0: + /classic-level@1.2.0: resolution: {integrity: sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg==} engines: {node: '>=12'} requiresBuild: true @@ -664,19 +696,19 @@ packages: node-gyp-build: 4.4.0 dev: false - /cli-cursor/4.0.0: + /cli-cursor@4.0.0: resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: restore-cursor: 4.0.0 dev: false - /cli-spinners/2.6.1: + /cli-spinners@2.6.1: resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==} engines: {node: '>=6'} dev: false - /cliui/7.0.4: + /cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: string-width: 4.2.3 @@ -684,44 +716,44 @@ packages: wrap-ansi: 7.0.0 dev: false - /clone/1.0.4: + /clone@1.0.4: resolution: {integrity: sha1-2jCcwmPfFZlMaIypAheco8fNfH4=} engines: {node: '>=0.8'} dev: false - /color-convert/1.9.3: + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 dev: false - /color-convert/2.0.1: + /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 dev: false - /color-name/1.1.3: + /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} dev: false - /color-name/1.1.4: + /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: false - /combined-stream/1.0.8: + /combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 dev: false - /concat-map/0.0.1: + /concat-map@0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} dev: false - /concurrently/7.1.0: + /concurrently@7.1.0: resolution: {integrity: sha512-Bz0tMlYKZRUDqJlNiF/OImojMB9ruKUz6GCfmhFnSapXgPe+3xzY4byqoKG9tUZ7L2PGEUjfLPOLfIX3labnmw==} engines: {node: ^12.20.0 || ^14.13.0 || >=16.0.0} hasBin: true @@ -736,11 +768,11 @@ packages: yargs: 16.2.0 dev: false - /confusing-browser-globals/1.0.11: + /confusing-browser-globals@1.0.11: resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} dev: false - /cross-spawn/7.0.3: + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} dependencies: @@ -749,12 +781,12 @@ packages: which: 2.0.2 dev: false - /date-fns/2.28.0: + /date-fns@2.28.0: resolution: {integrity: sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==} engines: {node: '>=0.11'} dev: false - /debug/2.6.9: + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: supports-color: '*' @@ -765,7 +797,7 @@ packages: ms: 2.0.0 dev: false - /debug/3.1.0: + /debug@3.1.0: resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} peerDependencies: supports-color: '*' @@ -776,7 +808,7 @@ packages: ms: 2.0.0 dev: false - /debug/3.2.7: + /debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: supports-color: '*' @@ -787,19 +819,7 @@ packages: ms: 2.1.3 dev: false - /debug/4.3.3: - resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: false - - /debug/4.3.3_supports-color@8.1.1: + /debug@4.3.3(supports-color@8.1.1): resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} engines: {node: '>=6.0'} peerDependencies: @@ -812,74 +832,74 @@ packages: supports-color: 8.1.1 dev: false - /decamelize/4.0.0: + /decamelize@4.0.0: resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} engines: {node: '>=10'} dev: false - /deep-eql/3.0.1: + /deep-eql@3.0.1: resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==} engines: {node: '>=0.12'} dependencies: type-detect: 4.0.8 dev: false - /deep-is/0.1.4: + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: false - /defaults/1.0.3: + /defaults@1.0.3: resolution: {integrity: sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=} dependencies: clone: 1.0.4 dev: false - /define-properties/1.1.3: + /define-properties@1.1.3: resolution: {integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==} engines: {node: '>= 0.4'} dependencies: object-keys: 1.1.1 dev: false - /delayed-stream/1.0.0: + /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} dev: false - /diff-sequences/27.5.1: + /diff-sequences@27.5.1: resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dev: false - /diff/5.0.0: + /diff@5.0.0: resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} engines: {node: '>=0.3.1'} dev: false - /doctrine/2.1.0: + /doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} dependencies: esutils: 2.0.3 dev: false - /doctrine/3.0.0: + /doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} dependencies: esutils: 2.0.3 dev: false - /dotenv/16.0.3: + /dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} dev: true - /emoji-regex/8.0.0: + /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: false - /es-abstract/1.19.1: + /es-abstract@1.19.1: resolution: {integrity: sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==} engines: {node: '>= 0.4'} dependencies: @@ -905,7 +925,7 @@ packages: unbox-primitive: 1.0.1 dev: false - /es-to-primitive/1.2.1: + /es-to-primitive@1.2.1: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} dependencies: @@ -914,27 +934,27 @@ packages: is-symbol: 1.0.4 dev: false - /escalade/3.1.1: + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} dev: false - /escape-string-regexp/1.0.5: + /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} dev: false - /escape-string-regexp/2.0.0: + /escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} dev: false - /escape-string-regexp/4.0.0: + /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} dev: false - /eslint-config-airbnb-base/15.0.0_rnagsyfcubvpoxo2ynj23pim7u: + /eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.25.4)(eslint@8.10.0): resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -943,13 +963,13 @@ packages: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.10.0 - eslint-plugin-import: 2.25.4_eslint@8.10.0 + eslint-plugin-import: 2.25.4(eslint@8.10.0) object.assign: 4.1.2 object.entries: 1.1.5 semver: 6.3.0 dev: false - /eslint-import-resolver-node/0.3.6: + /eslint-import-resolver-node@0.3.6: resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} dependencies: debug: 3.2.7 @@ -958,7 +978,7 @@ packages: - supports-color dev: false - /eslint-module-utils/2.7.3_ulu2225r2ychl26a37c6o2rfje: + /eslint-module-utils@2.7.3(eslint-import-resolver-node@0.3.6): resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==} engines: {node: '>=4'} peerDependencies: @@ -983,7 +1003,7 @@ packages: - supports-color dev: false - /eslint-plugin-import/2.25.4_eslint@8.10.0: + /eslint-plugin-import@2.25.4(eslint@8.10.0): resolution: {integrity: sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==} engines: {node: '>=4'} peerDependencies: @@ -999,7 +1019,7 @@ packages: doctrine: 2.1.0 eslint: 8.10.0 eslint-import-resolver-node: 0.3.6 - eslint-module-utils: 2.7.3_ulu2225r2ychl26a37c6o2rfje + eslint-module-utils: 2.7.3(eslint-import-resolver-node@0.3.6) has: 1.0.3 is-core-module: 2.8.1 is-glob: 4.0.3 @@ -1013,7 +1033,7 @@ packages: - supports-color dev: false - /eslint-scope/7.1.1: + /eslint-scope@7.1.1: resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: @@ -1021,7 +1041,7 @@ packages: estraverse: 5.3.0 dev: false - /eslint-utils/3.0.0_eslint@8.10.0: + /eslint-utils@3.0.0(eslint@8.10.0): resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: @@ -1031,17 +1051,17 @@ packages: eslint-visitor-keys: 2.1.0 dev: false - /eslint-visitor-keys/2.1.0: + /eslint-visitor-keys@2.1.0: resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} engines: {node: '>=10'} dev: false - /eslint-visitor-keys/3.3.0: + /eslint-visitor-keys@3.3.0: resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: false - /eslint/8.10.0: + /eslint@8.10.0: resolution: {integrity: sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true @@ -1051,11 +1071,11 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.3 + debug: 4.3.3(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.10.0 + eslint-utils: 3.0.0(eslint@8.10.0) eslint-visitor-keys: 3.3.0 espree: 9.3.1 esquery: 1.4.0 @@ -1085,40 +1105,40 @@ packages: - supports-color dev: false - /espree/9.3.1: + /espree@9.3.1: resolution: {integrity: sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.7.0 - acorn-jsx: 5.3.2_acorn@8.7.0 + acorn-jsx: 5.3.2(acorn@8.7.0) eslint-visitor-keys: 3.3.0 dev: false - /esquery/1.4.0: + /esquery@1.4.0: resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 dev: false - /esrecurse/4.3.0: + /esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} dependencies: estraverse: 5.3.0 dev: false - /estraverse/5.3.0: + /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} dev: false - /esutils/2.0.3: + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} dev: false - /expect/27.5.1: + /expect@27.5.1: resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: @@ -1128,40 +1148,40 @@ packages: jest-message-util: 27.5.1 dev: false - /fast-deep-equal/3.1.3: + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: false - /fast-json-stable-stringify/2.1.0: + /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: false - /fast-levenshtein/2.0.6: + /fast-levenshtein@2.0.6: resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=} dev: false - /file-entry-cache/6.0.1: + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: flat-cache: 3.0.4 dev: false - /fill-range/7.0.1: + /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 dev: false - /find-up/2.1.0: + /find-up@2.1.0: resolution: {integrity: sha1-RdG35QbHF93UgndaK3eSCjwMV6c=} engines: {node: '>=4'} dependencies: locate-path: 2.0.0 dev: false - /find-up/5.0.0: + /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} dependencies: @@ -1169,7 +1189,7 @@ packages: path-exists: 4.0.0 dev: false - /flat-cache/3.0.4: + /flat-cache@3.0.4: resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: @@ -1177,16 +1197,16 @@ packages: rimraf: 3.0.2 dev: false - /flat/5.0.2: + /flat@5.0.2: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true dev: false - /flatted/3.2.5: + /flatted@3.2.5: resolution: {integrity: sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==} dev: false - /follow-redirects/1.14.9: + /follow-redirects@1.14.9: resolution: {integrity: sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==} engines: {node: '>=4.0'} peerDependencies: @@ -1196,7 +1216,7 @@ packages: optional: true dev: false - /follow-redirects/1.5.10: + /follow-redirects@1.5.10: resolution: {integrity: sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==} engines: {node: '>=4.0'} dependencies: @@ -1205,7 +1225,7 @@ packages: - supports-color dev: false - /form-data/3.0.1: + /form-data@3.0.1: resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} engines: {node: '>= 6'} dependencies: @@ -1214,11 +1234,11 @@ packages: mime-types: 2.1.34 dev: false - /fs.realpath/1.0.0: + /fs.realpath@1.0.0: resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} dev: false - /fsevents/2.3.2: + /fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -1226,24 +1246,24 @@ packages: dev: false optional: true - /function-bind/1.1.1: + /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: false - /functional-red-black-tree/1.0.1: + /functional-red-black-tree@1.0.1: resolution: {integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=} dev: false - /get-caller-file/2.0.5: + /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} dev: false - /get-func-name/2.0.0: + /get-func-name@2.0.0: resolution: {integrity: sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=} dev: false - /get-intrinsic/1.1.1: + /get-intrinsic@1.1.1: resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==} dependencies: function-bind: 1.1.1 @@ -1251,7 +1271,7 @@ packages: has-symbols: 1.0.2 dev: false - /get-symbol-description/1.0.0: + /get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} engines: {node: '>= 0.4'} dependencies: @@ -1259,21 +1279,21 @@ packages: get-intrinsic: 1.1.1 dev: false - /glob-parent/5.1.2: + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 dev: false - /glob-parent/6.0.2: + /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 dev: false - /glob/7.2.0: + /glob@7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} dependencies: fs.realpath: 1.0.0 @@ -1284,75 +1304,75 @@ packages: path-is-absolute: 1.0.1 dev: false - /globals/13.12.1: + /globals@13.12.1: resolution: {integrity: sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==} engines: {node: '>=8'} dependencies: type-fest: 0.20.2 dev: false - /graceful-fs/4.2.10: + /graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} dev: false - /growl/1.10.5: + /growl@1.10.5: resolution: {integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==} engines: {node: '>=4.x'} dev: false - /has-bigints/1.0.1: + /has-bigints@1.0.1: resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==} dev: false - /has-flag/3.0.0: + /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} dev: false - /has-flag/4.0.0: + /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} dev: false - /has-symbols/1.0.2: + /has-symbols@1.0.2: resolution: {integrity: sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==} engines: {node: '>= 0.4'} dev: false - /has-tostringtag/1.0.0: + /has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.2 dev: false - /has/1.0.3: + /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 dev: false - /he/1.2.0: + /he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true dev: false - /ieee754/1.2.1: + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false - /ignore/4.0.6: + /ignore@4.0.6: resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} engines: {node: '>= 4'} dev: false - /ignore/5.2.0: + /ignore@5.2.0: resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} engines: {node: '>= 4'} dev: false - /import-fresh/3.3.0: + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} dependencies: @@ -1360,23 +1380,23 @@ packages: resolve-from: 4.0.0 dev: false - /imurmurhash/0.1.4: + /imurmurhash@0.1.4: resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=} engines: {node: '>=0.8.19'} dev: false - /inflight/1.0.6: + /inflight@1.0.6: resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} dependencies: once: 1.4.0 wrappy: 1.0.2 dev: false - /inherits/2.0.4: + /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: false - /internal-slot/1.0.3: + /internal-slot@1.0.3: resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} engines: {node: '>= 0.4'} dependencies: @@ -1385,20 +1405,20 @@ packages: side-channel: 1.0.4 dev: false - /is-bigint/1.0.4: + /is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: has-bigints: 1.0.1 dev: false - /is-binary-path/2.1.0: + /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 dev: false - /is-boolean-object/1.1.2: + /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} dependencies: @@ -1406,74 +1426,74 @@ packages: has-tostringtag: 1.0.0 dev: false - /is-buffer/2.0.5: + /is-buffer@2.0.5: resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} engines: {node: '>=4'} dev: false - /is-callable/1.2.4: + /is-callable@1.2.4: resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==} engines: {node: '>= 0.4'} dev: false - /is-core-module/2.8.1: + /is-core-module@2.8.1: resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==} dependencies: has: 1.0.3 dev: false - /is-date-object/1.0.5: + /is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 dev: false - /is-extglob/2.1.1: + /is-extglob@2.1.1: resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} engines: {node: '>=0.10.0'} dev: false - /is-fullwidth-code-point/3.0.0: + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} dev: false - /is-glob/4.0.3: + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 dev: false - /is-interactive/2.0.0: + /is-interactive@2.0.0: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} dev: false - /is-negative-zero/2.0.2: + /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} dev: false - /is-number-object/1.0.6: + /is-number-object@1.0.6: resolution: {integrity: sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==} engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 dev: false - /is-number/7.0.0: + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} dev: false - /is-plain-obj/2.1.0: + /is-plain-obj@2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} dev: false - /is-regex/1.1.4: + /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} dependencies: @@ -1481,45 +1501,45 @@ packages: has-tostringtag: 1.0.0 dev: false - /is-shared-array-buffer/1.0.1: + /is-shared-array-buffer@1.0.1: resolution: {integrity: sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==} dev: false - /is-string/1.0.7: + /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 dev: false - /is-symbol/1.0.4: + /is-symbol@1.0.4: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.2 dev: false - /is-unicode-supported/0.1.0: + /is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} dev: false - /is-unicode-supported/1.2.0: + /is-unicode-supported@1.2.0: resolution: {integrity: sha512-wH+U77omcRzevfIG8dDhTS0V9zZyweakfD01FULl97+0EHiJTTZtJqxPSkIIo/SDPv/i07k/C9jAPY+jwLLeUQ==} engines: {node: '>=12'} dev: false - /is-weakref/1.0.2: + /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 dev: false - /isexe/2.0.0: + /isexe@2.0.0: resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} dev: false - /jest-diff/27.5.1: + /jest-diff@27.5.1: resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: @@ -1529,12 +1549,12 @@ packages: pretty-format: 27.5.1 dev: false - /jest-get-type/27.5.1: + /jest-get-type@27.5.1: resolution: {integrity: sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dev: false - /jest-matcher-utils/27.5.1: + /jest-matcher-utils@27.5.1: resolution: {integrity: sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: @@ -1544,7 +1564,7 @@ packages: pretty-format: 27.5.1 dev: false - /jest-message-util/27.5.1: + /jest-message-util@27.5.1: resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: @@ -1559,7 +1579,7 @@ packages: stack-utils: 2.0.5 dev: false - /jest-mock/27.5.1: + /jest-mock@27.5.1: resolution: {integrity: sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: @@ -1567,7 +1587,7 @@ packages: '@types/node': 18.0.1 dev: false - /jest-util/27.5.1: + /jest-util@27.5.1: resolution: {integrity: sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: @@ -1579,51 +1599,51 @@ packages: picomatch: 2.3.1 dev: false - /js-sha3/0.8.0: + /js-sha3@0.8.0: resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} dev: false - /js-tokens/4.0.0: + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: false - /js-yaml/4.1.0: + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true dependencies: argparse: 2.0.1 dev: false - /json-schema-traverse/0.4.1: + /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: false - /json-schema-traverse/1.0.0: + /json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} dev: false - /json-stable-stringify-without-jsonify/1.0.1: + /json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=} dev: false - /json5/1.0.1: + /json5@1.0.1: resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==} hasBin: true dependencies: minimist: 1.2.5 dev: false - /kleur/3.0.3: + /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} dev: false - /level-supports/4.0.1: + /level-supports@4.0.1: resolution: {integrity: sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==} engines: {node: '>=12'} dev: false - /level-transcoder/1.0.1: + /level-transcoder@1.0.1: resolution: {integrity: sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==} engines: {node: '>=12'} dependencies: @@ -1631,7 +1651,7 @@ packages: module-error: 1.0.2 dev: false - /level/8.0.0: + /level@8.0.0: resolution: {integrity: sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==} engines: {node: '>=12'} dependencies: @@ -1639,7 +1659,7 @@ packages: classic-level: 1.2.0 dev: false - /levn/0.4.1: + /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} dependencies: @@ -1647,7 +1667,7 @@ packages: type-check: 0.4.0 dev: false - /locate-path/2.0.0: + /locate-path@2.0.0: resolution: {integrity: sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=} engines: {node: '>=4'} dependencies: @@ -1655,26 +1675,26 @@ packages: path-exists: 3.0.0 dev: false - /locate-path/6.0.0: + /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} dependencies: p-locate: 5.0.0 dev: false - /lodash.merge/4.6.2: + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: false - /lodash.truncate/4.4.2: + /lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} dev: false - /lodash/4.17.21: + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: false - /log-symbols/4.1.0: + /log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} dependencies: @@ -1682,7 +1702,7 @@ packages: is-unicode-supported: 0.1.0 dev: false - /log-symbols/5.1.0: + /log-symbols@5.1.0: resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} engines: {node: '>=12'} dependencies: @@ -1690,17 +1710,17 @@ packages: is-unicode-supported: 1.2.0 dev: false - /long/4.0.0: + /long@4.0.0: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} dev: false - /loupe/2.3.4: + /loupe@2.3.4: resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==} dependencies: get-func-name: 2.0.0 dev: false - /micromatch/4.0.5: + /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} dependencies: @@ -1708,40 +1728,40 @@ packages: picomatch: 2.3.1 dev: false - /mime-db/1.51.0: + /mime-db@1.51.0: resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==} engines: {node: '>= 0.6'} dev: false - /mime-types/2.1.34: + /mime-types@2.1.34: resolution: {integrity: sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.51.0 dev: false - /mimic-fn/2.1.0: + /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} dev: false - /minimatch/3.0.4: + /minimatch@3.0.4: resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} dependencies: brace-expansion: 1.1.11 dev: false - /minimatch/3.1.2: + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 dev: false - /minimist/1.2.5: + /minimist@1.2.5: resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} dev: false - /mocha/9.2.1: + /mocha@9.2.1: resolution: {integrity: sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==} engines: {node: '>= 12.0.0'} hasBin: true @@ -1750,7 +1770,7 @@ packages: ansi-colors: 4.1.1 browser-stdout: 1.3.1 chokidar: 3.5.3 - debug: 4.3.3_supports-color@8.1.1 + debug: 4.3.3(supports-color@8.1.1) diff: 5.0.0 escape-string-regexp: 4.0.0 find-up: 5.0.0 @@ -1772,38 +1792,38 @@ packages: yargs-unparser: 2.0.0 dev: false - /module-error/1.0.2: + /module-error@1.0.2: resolution: {integrity: sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==} engines: {node: '>=10'} dev: false - /ms/2.0.0: + /ms@2.0.0: resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} dev: false - /ms/2.1.2: + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: false - /ms/2.1.3: + /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: false - /nanoid/3.2.0: + /nanoid@3.2.0: resolution: {integrity: sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true dev: false - /napi-macros/2.0.0: + /napi-macros@2.0.0: resolution: {integrity: sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==} dev: false - /natural-compare/1.4.0: + /natural-compare@1.4.0: resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} dev: false - /node-fetch/2.6.7: + /node-fetch@2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} peerDependencies: @@ -1815,36 +1835,36 @@ packages: whatwg-url: 5.0.0 dev: false - /node-forge/0.10.0: + /node-forge@0.10.0: resolution: {integrity: sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==} engines: {node: '>= 6.0.0'} dev: false - /node-forge/0.8.5: + /node-forge@0.8.5: resolution: {integrity: sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==} engines: {node: '>= 4.5.0'} dev: false - /node-gyp-build/4.4.0: + /node-gyp-build@4.4.0: resolution: {integrity: sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==} hasBin: true dev: false - /normalize-path/3.0.0: + /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} dev: false - /object-inspect/1.12.0: + /object-inspect@1.12.0: resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==} dev: false - /object-keys/1.1.1: + /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} dev: false - /object.assign/4.1.2: + /object.assign@4.1.2: resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==} engines: {node: '>= 0.4'} dependencies: @@ -1854,7 +1874,7 @@ packages: object-keys: 1.1.1 dev: false - /object.entries/1.1.5: + /object.entries@1.1.5: resolution: {integrity: sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==} engines: {node: '>= 0.4'} dependencies: @@ -1863,7 +1883,7 @@ packages: es-abstract: 1.19.1 dev: false - /object.values/1.1.5: + /object.values@1.1.5: resolution: {integrity: sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==} engines: {node: '>= 0.4'} dependencies: @@ -1872,20 +1892,20 @@ packages: es-abstract: 1.19.1 dev: false - /once/1.4.0: + /once@1.4.0: resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} dependencies: wrappy: 1.0.2 dev: false - /onetime/5.1.2: + /onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 dev: false - /optionator/0.9.1: + /optionator@0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} dependencies: @@ -1897,7 +1917,7 @@ packages: word-wrap: 1.2.3 dev: false - /ora/6.1.0: + /ora@6.1.0: resolution: {integrity: sha512-CxEP6845hLK+NHFWZ+LplGO4zfw4QSfxTlqMfvlJ988GoiUeZDMzCvqsZkFHv69sPICmJH1MDxZoQFOKXerAVw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: @@ -1912,85 +1932,85 @@ packages: wcwidth: 1.0.1 dev: false - /p-limit/1.3.0: + /p-limit@1.3.0: resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} engines: {node: '>=4'} dependencies: p-try: 1.0.0 dev: false - /p-limit/3.1.0: + /p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 dev: false - /p-locate/2.0.0: + /p-locate@2.0.0: resolution: {integrity: sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=} engines: {node: '>=4'} dependencies: p-limit: 1.3.0 dev: false - /p-locate/5.0.0: + /p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} dependencies: p-limit: 3.1.0 dev: false - /p-try/1.0.0: + /p-try@1.0.0: resolution: {integrity: sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=} engines: {node: '>=4'} dev: false - /parent-module/1.0.1: + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} dependencies: callsites: 3.1.0 dev: false - /path-exists/3.0.0: + /path-exists@3.0.0: resolution: {integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=} engines: {node: '>=4'} dev: false - /path-exists/4.0.0: + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} dev: false - /path-is-absolute/1.0.1: + /path-is-absolute@1.0.1: resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} engines: {node: '>=0.10.0'} dev: false - /path-key/3.1.1: + /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} dev: false - /path-parse/1.0.7: + /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: false - /pathval/1.1.1: + /pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: false - /picomatch/2.3.1: + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} dev: false - /prelude-ls/1.2.1: + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} dev: false - /pretty-format/27.5.1: + /pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: @@ -1999,7 +2019,7 @@ packages: react-is: 17.0.2 dev: false - /prompts/2.4.2: + /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} dependencies: @@ -2007,7 +2027,7 @@ packages: sisteransi: 1.0.5 dev: false - /protobufjs/6.11.2: + /protobufjs@6.11.2: resolution: {integrity: sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==} hasBin: true requiresBuild: true @@ -2027,26 +2047,26 @@ packages: long: 4.0.0 dev: false - /punycode/2.1.1: + /punycode@2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} dev: false - /queue-microtask/1.2.3: + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: false - /randombytes/2.1.0: + /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: safe-buffer: 5.2.1 dev: false - /react-is/17.0.2: + /react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} dev: false - /readable-stream/3.6.0: + /readable-stream@3.6.0: resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} engines: {node: '>= 6'} dependencies: @@ -2055,34 +2075,34 @@ packages: util-deprecate: 1.0.2 dev: false - /readdirp/3.6.0: + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 dev: false - /regexpp/3.2.0: + /regexpp@3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} engines: {node: '>=8'} dev: false - /require-directory/2.1.1: + /require-directory@2.1.1: resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} engines: {node: '>=0.10.0'} dev: false - /require-from-string/2.0.2: + /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} dev: false - /resolve-from/4.0.0: + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} dev: false - /resolve/1.22.0: + /resolve@1.22.0: resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} hasBin: true dependencies: @@ -2091,7 +2111,7 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: false - /restore-cursor/4.0.0: + /restore-cursor@4.0.0: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: @@ -2099,54 +2119,54 @@ packages: signal-exit: 3.0.7 dev: false - /rimraf/3.0.2: + /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true dependencies: glob: 7.2.0 dev: false - /run-parallel-limit/1.1.0: + /run-parallel-limit@1.1.0: resolution: {integrity: sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==} dependencies: queue-microtask: 1.2.3 dev: false - /rxjs/6.6.7: + /rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} dependencies: tslib: 1.14.1 dev: false - /safe-buffer/5.2.1: + /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: false - /semver/6.3.0: + /semver@6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true dev: false - /serialize-javascript/6.0.0: + /serialize-javascript@6.0.0: resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} dependencies: randombytes: 2.1.0 dev: false - /shebang-command/2.0.0: + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 dev: false - /shebang-regex/3.0.0: + /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} dev: false - /side-channel/1.0.4: + /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: call-bind: 1.0.2 @@ -2154,20 +2174,20 @@ packages: object-inspect: 1.12.0 dev: false - /signal-exit/3.0.7: + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: false - /sisteransi/1.0.5: + /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} dev: false - /slash/3.0.0: + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} dev: false - /slice-ansi/4.0.0: + /slice-ansi@4.0.0: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} dependencies: @@ -2176,18 +2196,18 @@ packages: is-fullwidth-code-point: 3.0.0 dev: false - /spawn-command/0.0.2-1: + /spawn-command@0.0.2-1: resolution: {integrity: sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=} dev: false - /stack-utils/2.0.5: + /stack-utils@2.0.5: resolution: {integrity: sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==} engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 dev: false - /string-width/4.2.3: + /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} dependencies: @@ -2196,77 +2216,77 @@ packages: strip-ansi: 6.0.1 dev: false - /string.prototype.trimend/1.0.4: + /string.prototype.trimend@1.0.4: resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==} dependencies: call-bind: 1.0.2 define-properties: 1.1.3 dev: false - /string.prototype.trimstart/1.0.4: + /string.prototype.trimstart@1.0.4: resolution: {integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==} dependencies: call-bind: 1.0.2 define-properties: 1.1.3 dev: false - /string_decoder/1.3.0: + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 dev: false - /strip-ansi/6.0.1: + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 dev: false - /strip-ansi/7.0.1: + /strip-ansi@7.0.1: resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 dev: false - /strip-bom/3.0.0: + /strip-bom@3.0.0: resolution: {integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=} engines: {node: '>=4'} dev: false - /strip-json-comments/3.1.1: + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} dev: false - /supports-color/5.5.0: + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} dependencies: has-flag: 3.0.0 dev: false - /supports-color/7.2.0: + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} dependencies: has-flag: 4.0.0 dev: false - /supports-color/8.1.1: + /supports-color@8.1.1: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} dependencies: has-flag: 4.0.0 dev: false - /supports-preserve-symlinks-flag/1.0.0: + /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} dev: false - /table/6.8.0: + /table@6.8.0: resolution: {integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==} engines: {node: '>=10.0.0'} dependencies: @@ -2277,27 +2297,27 @@ packages: strip-ansi: 6.0.1 dev: false - /text-table/0.2.0: + /text-table@0.2.0: resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=} dev: false - /to-regex-range/5.0.1: + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 dev: false - /tr46/0.0.3: + /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false - /tree-kill/1.2.2: + /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true dev: false - /tsconfig-paths/3.12.0: + /tsconfig-paths@3.12.0: resolution: {integrity: sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==} dependencies: '@types/json5': 0.0.29 @@ -2306,32 +2326,32 @@ packages: strip-bom: 3.0.0 dev: false - /tslib/1.14.1: + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: false - /type-check/0.4.0: + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 dev: false - /type-detect/4.0.8: + /type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} dev: false - /type-fest/0.20.2: + /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} dev: false - /typed-ts-events/1.2.1: + /typed-ts-events@1.2.1: resolution: {integrity: sha512-+Fy9cqWA/Kv1QX0k6m5ZflGcG2jQSZQGr+jLGXYUM22yihhkHs243LEXvY4cs54lAVyj5gokm0TbgkmL4qDsTg==} dev: false - /unbox-primitive/1.0.1: + /unbox-primitive@1.0.1: resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==} dependencies: function-bind: 1.1.1 @@ -2340,38 +2360,38 @@ packages: which-boxed-primitive: 1.0.2 dev: false - /uri-js/4.4.1: + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.1.1 dev: false - /util-deprecate/1.0.2: + /util-deprecate@1.0.2: resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} dev: false - /v8-compile-cache/2.3.0: + /v8-compile-cache@2.3.0: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} dev: false - /wcwidth/1.0.1: + /wcwidth@1.0.1: resolution: {integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=} dependencies: defaults: 1.0.3 dev: false - /webidl-conversions/3.0.1: + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false - /whatwg-url/5.0.0: + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 dev: false - /which-boxed-primitive/1.0.2: + /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: is-bigint: 1.0.4 @@ -2381,7 +2401,7 @@ packages: is-symbol: 1.0.4 dev: false - /which/2.0.2: + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true @@ -2389,16 +2409,16 @@ packages: isexe: 2.0.0 dev: false - /word-wrap/1.2.3: + /word-wrap@1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} dev: false - /workerpool/6.2.0: + /workerpool@6.2.0: resolution: {integrity: sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==} dev: false - /wrap-ansi/7.0.0: + /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} dependencies: @@ -2407,21 +2427,21 @@ packages: strip-ansi: 6.0.1 dev: false - /wrappy/1.0.2: + /wrappy@1.0.2: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} dev: false - /y18n/5.0.8: + /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} dev: false - /yargs-parser/20.2.4: + /yargs-parser@20.2.4: resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} engines: {node: '>=10'} dev: false - /yargs-unparser/2.0.0: + /yargs-unparser@2.0.0: resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} engines: {node: '>=10'} dependencies: @@ -2431,7 +2451,7 @@ packages: is-plain-obj: 2.1.0 dev: false - /yargs/16.2.0: + /yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} dependencies: @@ -2444,7 +2464,7 @@ packages: yargs-parser: 20.2.4 dev: false - /yocto-queue/0.1.0: + /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: false From d741c48a90b5d1fbf242914caa43ad4edd434ca6 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 15 Aug 2023 17:57:27 +0500 Subject: [PATCH 63/86] remove some tests --- ...easeLockRejectIfDurationIsLessThenZero.mjs | 97 ------------- .../increaseLockRejectIfInvalidAsset.mjs | 120 ---------------- ...ncreaseLockRejectIfPaymentsMoreThanOne.mjs | 129 ------------------ .../boosting/lockRejectIfLockedWXs.mjs | 96 ------------- 4 files changed, 442 deletions(-) delete mode 100644 test/components/boosting/increaseLockRejectIfDurationIsLessThenZero.mjs delete mode 100644 test/components/boosting/increaseLockRejectIfInvalidAsset.mjs delete mode 100644 test/components/boosting/increaseLockRejectIfPaymentsMoreThanOne.mjs delete mode 100644 test/components/boosting/lockRejectIfLockedWXs.mjs diff --git a/test/components/boosting/increaseLockRejectIfDurationIsLessThenZero.mjs b/test/components/boosting/increaseLockRejectIfDurationIsLessThenZero.mjs deleted file mode 100644 index 9a74088a0..000000000 --- a/test/components/boosting/increaseLockRejectIfDurationIsLessThenZero.mjs +++ /dev/null @@ -1,97 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import { - transfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: increaseLockRejectIfDurationIsLessThenZero.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should reject increaseLock', - async function () { - const deltaDuration = -1; - const duration = this.maxDuration - 1; - const assetAmount = this.minLockAmount; - const referrer = ''; - const signature = 'base64:'; - - const expectedRejectMessage = 'duration is less then zero'; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const lockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: duration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - await broadcastAndWait(lockRefTx); - - const increaseLockTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'increaseLock', - args: [ - { type: 'integer', value: deltaDuration }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect( - api.transactions.broadcast(increaseLockTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); diff --git a/test/components/boosting/increaseLockRejectIfInvalidAsset.mjs b/test/components/boosting/increaseLockRejectIfInvalidAsset.mjs deleted file mode 100644 index bce439f7b..000000000 --- a/test/components/boosting/increaseLockRejectIfInvalidAsset.mjs +++ /dev/null @@ -1,120 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import { - issue, - transfer, - massTransfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: increaseLockRejectIfInvalidAsset.mjs', /** @this {MochaSuiteModified} */() => { - const seed = 'waves private node seed with waves tokens'; - let someAssetId; - - before(async function () { - const someIssueTx = issue({ - name: 'Some Token', - description: '', - quantity: 1e16, - decimals: 8, - chainId, - }, seed); - await broadcastAndWait(someIssueTx); - someAssetId = someIssueTx.id; - - const someAmount = 1e16; - const massTransferTxWX = massTransfer({ - transfers: [{ recipient: this.accounts.user0.addr, amount: someAmount }], - assetId: someAssetId, - chainId, - }, seed); - await broadcastAndWait(massTransferTxWX); - }); - it( - 'should reject increaseLock', - async function () { - const deltaDuration = 0; - const duration = this.maxDuration - 1; - const assetAmount = this.minLockAmount; - const referrer = ''; - const signature = 'base64:'; - - const expectedRejectMessage = 'invalid asset id in payment'; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const lockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: duration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - await broadcastAndWait(lockRefTx); - - const increaseLockTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: someAssetId, amount: assetAmount }, - ], - call: { - function: 'increaseLock', - args: [ - { type: 'integer', value: deltaDuration }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect( - api.transactions.broadcast(increaseLockTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); diff --git a/test/components/boosting/increaseLockRejectIfPaymentsMoreThanOne.mjs b/test/components/boosting/increaseLockRejectIfPaymentsMoreThanOne.mjs deleted file mode 100644 index df895c77f..000000000 --- a/test/components/boosting/increaseLockRejectIfPaymentsMoreThanOne.mjs +++ /dev/null @@ -1,129 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import { address } from '@waves/ts-lib-crypto'; -import { - transfer, - massTransfer, - issue, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: increaseLockRejectIfPaymentsMoreThanOne.mjs', /** @this {MochaSuiteModified} */() => { - const seed = 'waves private node seed with waves tokens'; - let someAssetId; - let user0; - let boosting; - let wxAssetId; - - before(async function () { - user0 = this.accounts.user0; - boosting = this.accounts.boosting.addr; - wxAssetId = this.wxAssetId; - - const someIssueTx = issue({ - name: 'Some Token', - description: '', - quantity: 1e16, - decimals: 8, - chainId, - }, seed); - await broadcastAndWait(someIssueTx); - someAssetId = someIssueTx.id; - - const someAmount = 1e16; - const massTransferTxWX = massTransfer({ - transfers: [{ recipient: address(user0, chainId), amount: someAmount }], - assetId: someAssetId, - chainId, - }, seed); - await broadcastAndWait(massTransferTxWX); - }); - it( - 'should reject increaseLock', - async function () { - const deltaDuration = 0; - const duration = this.maxDuration - 1; - const assetAmount = this.minLockAmount; - const referrer = ''; - const signature = 'base64:'; - - const expectedRejectMessage = 'only one payment is allowed'; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const lockRefTx = invokeScript({ - dApp: boosting, - payment: [ - { assetId: wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: duration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - await broadcastAndWait(lockRefTx); - - const increaseLockTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: wxAssetId, amount: assetAmount }, - { assetId: someAssetId, amount: assetAmount }, - ], - call: { - function: 'increaseLock', - args: [ - { type: 'integer', value: deltaDuration }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect( - api.transactions.broadcast(increaseLockTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); diff --git a/test/components/boosting/lockRejectIfLockedWXs.mjs b/test/components/boosting/lockRejectIfLockedWXs.mjs deleted file mode 100644 index bb840e850..000000000 --- a/test/components/boosting/lockRejectIfLockedWXs.mjs +++ /dev/null @@ -1,96 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import { - data, - transfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: lockRejectIfLockedWXs.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should reject lockRef', - async function () { - const duration = this.maxDuration - 1; - const referrer = ''; - const signature = 'base64:'; - - const assetAmount = this.minLockAmount; - const paramByUserNum = 1; - - const expectedRejectMessage = 'there are locked WXs - consider to use increaseLock %s%d%s__paramByUserNum__0__amount'; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const setParamByUserNumStartTx = data({ - additionalFee: 4e5, - data: [ - { - key: '%s%d%s__paramByUserNum__0__amount', - type: 'integer', - value: paramByUserNum, - }, - ], - chainId, - }, this.accounts.boosting.seed); - await broadcastAndWait(setParamByUserNumStartTx); - - const lockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: duration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect( - api.transactions.broadcast(lockRefTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); From ac797b20e8bce3cd1963f6cb928036ad9e8f14c9 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 15 Aug 2023 18:59:24 +0500 Subject: [PATCH 64/86] remove some tests --- ...claimWxBoostRejectIfUnsupportedLpAsset.mjs | 2 +- ...WxBoostRejectIfUnsupportedLpAssetEmpty.mjs | 84 ---------------- .../getUserGwxAmountAtHeightREADONLY.mjs | 99 ------------------- ...ctIfDurationGreaterThenMaxLockDuration.mjs | 82 --------------- ...ejectIfDurationLessThenMinLockDuration.mjs | 82 --------------- .../boosting/lockRefRejectIfIsActiveLock.mjs | 98 ------------------ .../boosting/lockRefRejectIfLockedWXs.mjs | 96 ------------------ ...ctIfDurationGreaterThenMaxLockDuration.mjs | 82 --------------- ...ejectIfDurationLessThenMinLockDuration.mjs | 82 --------------- .../boosting/lockRejectIfIsActiveLock.mjs | 99 ------------------- .../boosting/unlockRejectIfLockMoreHeight.mjs | 88 ----------------- 11 files changed, 1 insertion(+), 893 deletions(-) delete mode 100644 test/components/boosting/claimWxBoostRejectIfUnsupportedLpAssetEmpty.mjs delete mode 100644 test/components/boosting/getUserGwxAmountAtHeightREADONLY.mjs delete mode 100644 test/components/boosting/lockRefRejectIfDurationGreaterThenMaxLockDuration.mjs delete mode 100644 test/components/boosting/lockRefRejectIfDurationLessThenMinLockDuration.mjs delete mode 100644 test/components/boosting/lockRefRejectIfIsActiveLock.mjs delete mode 100644 test/components/boosting/lockRefRejectIfLockedWXs.mjs delete mode 100644 test/components/boosting/lockRejectIfDurationGreaterThenMaxLockDuration.mjs delete mode 100644 test/components/boosting/lockRejectIfDurationLessThenMinLockDuration.mjs delete mode 100644 test/components/boosting/lockRejectIfIsActiveLock.mjs delete mode 100644 test/components/boosting/unlockRejectIfLockMoreHeight.mjs diff --git a/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAsset.mjs b/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAsset.mjs index d8d346221..b65d4d3af 100644 --- a/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAsset.mjs +++ b/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAsset.mjs @@ -55,7 +55,7 @@ describe('boosting: claimWxBoostRejectIfUnsupportedLpAsset.mjs', /** @this {Moch const { height: lockStartHeight } = await boosting.lock({ dApp: this.accounts.boosting.addr, caller: this.accounts.user0.seed, - duration: this.maxLockDuration, + duration: 1, payments: [{ assetId: this.wxAssetId, amount: wxAmount }], }); await waitForHeight(lockStartHeight + 1); diff --git a/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAssetEmpty.mjs b/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAssetEmpty.mjs deleted file mode 100644 index b5126dfaf..000000000 --- a/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAssetEmpty.mjs +++ /dev/null @@ -1,84 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import { - transfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait, waitForHeight } from '../../utils/api.mjs'; -import { boosting } from './contract/boosting.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: claimWxBoostRejectIfUnsupportedLpAssetEmpty.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should reject claimWxBoost', - async function () { - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - const emptyLpAssetId = 'empty'; - - const expectedRejectMessage = 'not readonly mode: unsupported lp asset empty'; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const { height: lockStartHeight } = await boosting.lock({ - dApp: this.accounts.boosting.addr, - caller: this.accounts.user0.seed, - duration: this.maxLockDuration, - payments: [{ assetId: this.wxAssetId, amount: wxAmount }], - }); - await waitForHeight(lockStartHeight + 1); - - const claimWxBoostTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [], - call: { - function: 'claimWxBoost', - args: [ - { type: 'string', value: emptyLpAssetId }, - { type: 'string', value: this.accounts.user0.addr }, - ], - }, - additionalFee: 4e5, - chainId, - }, this.accounts.staking.seed); - - await expect( - api.transactions.broadcast(claimWxBoostTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); diff --git a/test/components/boosting/getUserGwxAmountAtHeightREADONLY.mjs b/test/components/boosting/getUserGwxAmountAtHeightREADONLY.mjs deleted file mode 100644 index 3d41e61aa..000000000 --- a/test/components/boosting/getUserGwxAmountAtHeightREADONLY.mjs +++ /dev/null @@ -1,99 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import { - data, - transfer, - reissue, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait, waitForHeight } from '../../utils/api.mjs'; -import { boosting } from './contract/boosting.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: getUserGwxAmountAtHeightREADONLY.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should successfully getUserGwxAmountAtHeightREADONLY', - async function () { - const k = 1000; - const b = 10; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const { height: lockStartHeight } = await boosting.lock({ - dApp: this.accounts.boosting.addr, - caller: this.accounts.user0.seed, - duration: this.maxLockDuration, - payments: [{ assetId: this.wxAssetId, amount: wxAmount }], - }); - await waitForHeight(lockStartHeight + 1); - - const setKTx = data({ - additionalFee: 4e5, - data: [{ - key: '%s%d%s__paramByUserNum__0__k', - type: 'integer', - value: k, - }], - chainId, - }, this.accounts.boosting.seed); - await broadcastAndWait(setKTx); - - const setBTx = data({ - additionalFee: 4e5, - data: [{ - key: '%s%d%s__paramByUserNum__0__b', - type: 'integer', - value: b, - }], - chainId, - }, this.accounts.boosting.seed); - const { height } = await broadcastAndWait(setBTx); - - const expectedGwxAmount = Math.floor((k * height + b) / 1000); - - const expr = `getUserGwxAmountAtHeightREADONLY(\"${this.accounts.user0.addr}\", ${height})`; /* eslint-disable-line */ - const response = await api.utils.fetchEvaluate( - this.accounts.boosting.addr, - expr, - ); - const checkData = response.result.value._2; /* eslint-disable-line */ - - expect(checkData).to.eql({ - type: 'Int', - value: expectedGwxAmount, - }); - }, - ); -}); diff --git a/test/components/boosting/lockRefRejectIfDurationGreaterThenMaxLockDuration.mjs b/test/components/boosting/lockRefRejectIfDurationGreaterThenMaxLockDuration.mjs deleted file mode 100644 index 4c083776e..000000000 --- a/test/components/boosting/lockRefRejectIfDurationGreaterThenMaxLockDuration.mjs +++ /dev/null @@ -1,82 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import { - transfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: lockRefRejectIfDurationGreaterThenMaxLockDuration.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should reject lockRef', - async function () { - const durationLessThenMaxLockDuration = this.maxDuration + 1; - const referrer = ''; - const signature = 'base64:'; - - const assetAmount = this.minLockAmount; - - const expectedRejectMessage = `passed duration is greater then maxLockDuration=${this.maxDuration}`; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const lockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: durationLessThenMaxLockDuration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect( - api.transactions.broadcast(lockRefTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); diff --git a/test/components/boosting/lockRefRejectIfDurationLessThenMinLockDuration.mjs b/test/components/boosting/lockRefRejectIfDurationLessThenMinLockDuration.mjs deleted file mode 100644 index 750440e11..000000000 --- a/test/components/boosting/lockRefRejectIfDurationLessThenMinLockDuration.mjs +++ /dev/null @@ -1,82 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import { - transfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: lockRefRejectIfDurationLessThenMinLockDuration.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should reject lockRef', - async function () { - const durationLessThenMinLockDuration = this.minDuration - 1; - const referrer = ''; - const signature = 'base64:'; - - const assetAmount = this.minLockAmount; - - const expectedRejectMessage = `passed duration is less then minLockDuration=${this.minDuration}`; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const lockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: durationLessThenMinLockDuration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect( - api.transactions.broadcast(lockRefTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); diff --git a/test/components/boosting/lockRefRejectIfIsActiveLock.mjs b/test/components/boosting/lockRefRejectIfIsActiveLock.mjs deleted file mode 100644 index dc12a63bc..000000000 --- a/test/components/boosting/lockRefRejectIfIsActiveLock.mjs +++ /dev/null @@ -1,98 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import { - transfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: lockRefRejectIfIsActiveLock.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should reject lockRef', - async function () { - const duration = this.maxDuration - 1; - const referrer = ''; - const signature = 'base64:'; - - const assetAmount = this.minLockAmount; - - const expectedRejectMessage = 'there is an active lock - consider to use increaseLock'; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const fisrtLockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: duration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - await broadcastAndWait(fisrtLockRefTx); - - const secondLockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: duration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect( - api.transactions.broadcast(secondLockRefTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); diff --git a/test/components/boosting/lockRefRejectIfLockedWXs.mjs b/test/components/boosting/lockRefRejectIfLockedWXs.mjs deleted file mode 100644 index df02c307b..000000000 --- a/test/components/boosting/lockRefRejectIfLockedWXs.mjs +++ /dev/null @@ -1,96 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import { - data, - transfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: lockRefRejectIfLockedWXs.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should reject lockRef', - async function () { - const duration = this.maxDuration - 1; - const referrer = ''; - const signature = 'base64:'; - - const assetAmount = this.minLockAmount; - const paramByUserNum = 1; - - const expectedRejectMessage = 'there are locked WXs - consider to use increaseLock %s%d%s__paramByUserNum__0__amount'; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const setParamByUserNumStartTx = data({ - additionalFee: 4e5, - data: [ - { - key: '%s%d%s__paramByUserNum__0__amount', - type: 'integer', - value: paramByUserNum, - }, - ], - chainId, - }, this.accounts.boosting.seed); - await broadcastAndWait(setParamByUserNumStartTx); - - const lockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: duration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect( - api.transactions.broadcast(lockRefTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); diff --git a/test/components/boosting/lockRejectIfDurationGreaterThenMaxLockDuration.mjs b/test/components/boosting/lockRejectIfDurationGreaterThenMaxLockDuration.mjs deleted file mode 100644 index c2b9ad374..000000000 --- a/test/components/boosting/lockRejectIfDurationGreaterThenMaxLockDuration.mjs +++ /dev/null @@ -1,82 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import { - transfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: lockRejectIfDurationGreaterThenMaxLockDuration.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should reject lockRef', - async function () { - const durationLessThenMaxLockDuration = this.maxDuration + 1; - const referrer = ''; - const signature = 'base64:'; - - const assetAmount = this.minLockAmount; - - const expectedRejectMessage = `passed duration is greater then maxLockDuration=${this.maxDuration}`; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const lockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: durationLessThenMaxLockDuration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect( - api.transactions.broadcast(lockRefTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); diff --git a/test/components/boosting/lockRejectIfDurationLessThenMinLockDuration.mjs b/test/components/boosting/lockRejectIfDurationLessThenMinLockDuration.mjs deleted file mode 100644 index 0399f4c41..000000000 --- a/test/components/boosting/lockRejectIfDurationLessThenMinLockDuration.mjs +++ /dev/null @@ -1,82 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import { - transfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: lockRejectIfDurationLessThenMinLockDuration.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should reject lockRef', - async function () { - const durationLessThenMinLockDuration = this.minDuration - 1; - const referrer = ''; - const signature = 'base64:'; - - const assetAmount = this.minLockAmount; - - const expectedRejectMessage = `passed duration is less then minLockDuration=${this.minDuration}`; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const lockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: durationLessThenMinLockDuration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect( - api.transactions.broadcast(lockRefTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); diff --git a/test/components/boosting/lockRejectIfIsActiveLock.mjs b/test/components/boosting/lockRejectIfIsActiveLock.mjs deleted file mode 100644 index 3a4ffa4b9..000000000 --- a/test/components/boosting/lockRejectIfIsActiveLock.mjs +++ /dev/null @@ -1,99 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import { - transfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: lockRejectIfIsActiveLock.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should reject lockRef', - async function () { - const duration = this.maxDuration - 1; - const referrer = ''; - const signature = 'base64:'; - - const assetAmount = this.minLockAmount; - - const expectedRejectMessage = 'there is an active lock - consider to use increaseLock'; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const fisrtLockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: duration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - await broadcastAndWait(fisrtLockRefTx); - - const secondLockRefTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lockRef', - args: [ - { type: 'integer', value: duration }, - { type: 'string', value: referrer }, - { type: 'binary', value: signature }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect( - api.transactions.broadcast(secondLockRefTx, {}), - ).to.be.rejectedWith( - `Error while executing dApp: boosting.ride: ${expectedRejectMessage}`, - ); - }, - ); -}); diff --git a/test/components/boosting/unlockRejectIfLockMoreHeight.mjs b/test/components/boosting/unlockRejectIfLockMoreHeight.mjs deleted file mode 100644 index 91f1b32de..000000000 --- a/test/components/boosting/unlockRejectIfLockMoreHeight.mjs +++ /dev/null @@ -1,88 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import { - transfer, - reissue, - invokeScript, -} from '@waves/waves-transactions'; -import { create } from '@waves/node-api-js'; - -import { broadcastAndWait } from '../../utils/api.mjs'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -const apiBase = process.env.API_NODE_URL; -const chainId = 'R'; - -const api = create(apiBase); - -describe('boosting: unlockRejectIfLockMoreHeight.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should reject unlock', - async function () { - const duration = this.maxDuration - 1; - const assetAmount = this.minLockAmount; - - const lpAssetAmount = 1e3 * 1e8; - const wxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: wxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const lpAssetIssueTx = reissue({ - assetId: this.lpAssetId, - quantity: lpAssetAmount * 10, - reissuable: true, - chainId, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetIssueTx); - - const lpAssetTransferTx = transfer({ - recipient: this.accounts.user0.addr, - amount: lpAssetAmount, - assetId: this.lpAssetId, - additionalFee: 4e5, - }, this.accounts.factory.seed); - await broadcastAndWait(lpAssetTransferTx); - - const lockTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [ - { assetId: this.wxAssetId, amount: assetAmount }, - ], - call: { - function: 'lock', - args: [ - { type: 'integer', value: duration }, - ], - }, - chainId, - }, this.accounts.user0.seed); - const { height } = await broadcastAndWait(lockTx); - - const expectedRejectMessage = `wait ${height + duration} to unlock`; - - const unlockTx = invokeScript({ - dApp: this.accounts.boosting.addr, - payment: [], - call: { - function: 'unlock', - args: [ - { type: 'string', value: this.accounts.user0.addr }, - ], - }, - chainId, - }, this.accounts.user0.seed); - - await expect(api.transactions.broadcast(unlockTx, {})).to.be.rejectedWith( - expectedRejectMessage, - ); - }, - ); -}); From 48fa0235c07c90f6144b9153ae88c0aacd2f3296 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 17 Aug 2023 17:35:40 +0500 Subject: [PATCH 65/86] fix unlock --- ride/boosting.ride | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 08a32235d..c816e5430 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -328,7 +328,7 @@ func StatsEntry(totalLockedInc: Int, durationInc: Int, lockCountInc: Int, usersC func LockParamsEntry( userAddress: Address, - txId: ByteVector, + txId: ByteVector|Unit, amount: Int, start: Int, duration: Int, @@ -713,10 +713,10 @@ func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { func unlock(txIdStr: String) = { let userAddress = i.caller let userAddressStr = userAddress.toString() - let txId = txIdStr.fromBase58String() + let txIdOption = if (txIdStr == "") then unit else txIdStr.fromBase58String() let userRecordArray = readLockParamsRecordOrFail( userAddress, - if (txIdStr == "") then unit else txId + txIdOption ) let userAmount = userRecordArray[IdxLockAmount].parseIntValue() @@ -729,7 +729,7 @@ func unlock(txIdStr: String) = { let wxWithdrawable = getWxWithdrawable( userAddress, - if (txIdStr == "") then unit else txId + txIdOption ) let gWxAmountStart = fraction(userAmount, lockDuration, maxLockDuration) @@ -754,7 +754,7 @@ func unlock(txIdStr: String) = { .valueOrErrorMessage(wrapErr("invalid user number")).parseIntValue() strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes, userNum], []) - LockParamsEntry(userAddress, txId, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + wxWithdrawable) + LockParamsEntry(userAddress, txIdOption, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + wxWithdrawable) ++ StatsEntry(-wxWithdrawable, 0, 0, 0) :+ HistoryEntry("unlock", userAddressStr, wxWithdrawable, lockStart, lockDuration, gwxBurned, i) :+ ScriptTransfer(userAddress, wxWithdrawable, assetId) From 7f3c5677182e1ea9659ff70d93901fff1a7608bc Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 17 Aug 2023 17:48:41 +0500 Subject: [PATCH 66/86] gwx reward test draft + replace invokes --- ride/gwx_reward.ride | 17 +++++++++++-- test/components/gwx_reward/_hooks.mjs | 2 ++ .../gwx_reward/contract/gwxReward.mjs | 24 +++++++++++++++++++ .../gwx_reward/refreshUserReward.mjs | 22 +++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 test/components/gwx_reward/refreshUserReward.mjs diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index e1b895455..ff2b38c83 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -215,6 +215,19 @@ func getTradingReward(userAddress: String) = { func keyRewardPerGwxIntegral() = ["%s", "rewardPerGwxIntegral"].makeString(SEP) +func getGwxAmountTotalOption() = { + let keyGwxTotal = "%s%s__gwx__total" + boostingContractOrFail().getInteger(keyGwxTotal) +} + +func getUserGwxAmountTotalOption(userAddress: Address) = { + func keyUserGwxAmountTotal(userAddress: Address) = makeString([ + "%s%s__gwxAmountTotal", + userAddress.toString() + ], SEP) + boostingContractOrFail().getInteger(keyUserGwxAmountTotal(userAddress)) +} + # call when reward or total gwx amount are changed func _refreshRewardPerGwxIntegral() = { # rewardPerGwx = dh * emissionPerBlock / totalGwxAmount + rewardPerGwxPrevious @@ -230,7 +243,7 @@ func _refreshRewardPerGwxIntegral() = { .valueOrErrorMessage(wrapErr("invalid " + keyRatePerBlockCurrent())) let gwxHoldersRewardCurrent = emissionContract.getInteger(keyGwxHoldersRewardCurrent()) .valueOrElse(0) - let gwxAmountTotal = boostingContractOrFail().invoke("getGwxTotalREADONLY", [], []).exactAs[Int] + let gwxAmountTotal = getGwxAmountTotalOption().valueOrElse(0) let dh = { height - rewardPerGwxIntegralLastHeight }.toBigInt() let gwxAmountTotalBI = gwxAmountTotal.toBigInt() let rewardPerGwxIntegral = rewardPerGwxIntegralPrevious + if (gwxAmountTotalBI == zeroBigInt) then zeroBigInt else fraction( @@ -265,7 +278,7 @@ func _refreshUserReward(userAddress: Address, userNum: Int) = { let userUnclaimed = keyUserUnclaimed(userNum).getInteger().valueOrElse(0) # userReward = userGwxAmount * (rewardPerGwxIntegral - rewardPerGwxIntegralUserLast) + userRewardPrevious - userRewardClaimed - let userGwxAmount = boostingContractOrFail().invoke("getUserGwxAmount", [userAddress.toString()], []).exactAs[Int] + let userGwxAmount = getUserGwxAmountTotalOption(userAddress).valueOrElse(0) let userReward = fraction(userGwxAmount.toBigInt(), rewardPerGwxIntegral - rewardPerGwxIntegralUserLast, MULT18BI).toInt() + userUnclaimed ( diff --git a/test/components/gwx_reward/_hooks.mjs b/test/components/gwx_reward/_hooks.mjs index 14ca3921e..8144ce16b 100644 --- a/test/components/gwx_reward/_hooks.mjs +++ b/test/components/gwx_reward/_hooks.mjs @@ -24,6 +24,7 @@ export const mochaHooks = { const contractNames = [ 'gwxReward', 'emission', + 'boosting', ]; const userNames = Array.from({ length: 3 }, (_, k) => `user${k}`); const names = [...contractNames, ...userNames, 'pacemaker']; @@ -73,6 +74,7 @@ export const mochaHooks = { await gwxReward.init({ caller: this.accounts.gwxReward.seed, + boostingContractAddress: this.accounts.boosting.addr, emissionAddress: this.accounts.emission.addr, wxAssetId: this.wxAssetId, maxRecipients: 90, diff --git a/test/components/gwx_reward/contract/gwxReward.mjs b/test/components/gwx_reward/contract/gwxReward.mjs index 3fa8b2804..8a0f07cd9 100644 --- a/test/components/gwx_reward/contract/gwxReward.mjs +++ b/test/components/gwx_reward/contract/gwxReward.mjs @@ -1,4 +1,5 @@ import { data, invokeScript } from '@waves/waves-transactions'; +import { base58Decode, base64Encode } from '@waves/ts-lib-crypto'; import { broadcastAndWait, chainId } from '../../../utils/api.mjs'; export const gwxReward = { @@ -7,9 +8,12 @@ export const gwxReward = { emissionAddress, wxAssetId, maxRecipients, + matcherPacemakerAddress = '', + boostingContractAddress, }) => { const dataTx = data({ data: [ + { key: '%s__config', type: 'string', value: `%s%s%s__${wxAssetId}__${matcherPacemakerAddress}__${boostingContractAddress}` }, { key: '%s%s__config__emissionAddress', type: 'string', value: emissionAddress }, { key: '%s__wxAssetId', type: 'string', value: wxAssetId }, { key: '%s__maxRecipients', type: 'integer', value: maxRecipients }, @@ -79,4 +83,24 @@ export const gwxReward = { ); return broadcastAndWait(invokeTx); }, + + refreshUserReward: async ({ + caller, + gwxRewardAddress, + userAddress, + userNum, + }) => broadcastAndWait( + invokeScript({ + dApp: gwxRewardAddress, + call: { + function: 'refreshUserReward', + args: [ + { type: 'binary', value: base64Encode(base58Decode(userAddress)) }, + { type: 'integer', value: userNum }, + ], + }, + additionalFee: 4e5, + chainId, + }, caller), + ), }; diff --git a/test/components/gwx_reward/refreshUserReward.mjs b/test/components/gwx_reward/refreshUserReward.mjs new file mode 100644 index 000000000..8badd48cf --- /dev/null +++ b/test/components/gwx_reward/refreshUserReward.mjs @@ -0,0 +1,22 @@ +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { gwxReward } from './contract/gwxReward.mjs'; + +chai.use(chaiAsPromised); + +describe('gwxReward: refreshUserReward.mjs', /** @this {MochaSuiteModified} */() => { + it( + 'should successfully refresh user reward', + async function () { + const { stateChanges } = await gwxReward.refreshUserReward({ + caller: this.accounts.boosting.seed, + gwxRewardAddress: this.accounts.gwxReward.addr, + userAddress: this.accounts.user0.addr, + userNum: 0, + }); + + // TODO: check stateChanges + }, + ); +}); From 02cf7261dadc765f6de1085cc7128a92ef91d17d Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 18 Aug 2023 14:52:29 +0500 Subject: [PATCH 67/86] boosting unlock fix --- migrations/wxdefi-435-release/index.mjs | 5 +-- ride/boosting.ride | 51 +++---------------------- 2 files changed, 8 insertions(+), 48 deletions(-) diff --git a/migrations/wxdefi-435-release/index.mjs b/migrations/wxdefi-435-release/index.mjs index 2905f9f2e..7640a9cc0 100644 --- a/migrations/wxdefi-435-release/index.mjs +++ b/migrations/wxdefi-435-release/index.mjs @@ -21,6 +21,7 @@ const { MIN_LOCK_DURATION, MAX_LOCK_DURATION, BLOCKS_IN_PERIOD, + LOCK_STEP_BLOCKS, } = process.env; const api = create(NODE_URL); @@ -34,9 +35,6 @@ const gwxRewardAddress = address( CHAIN_ID ); -const keyLockParamsRecordOld = (userAddress) => - `%s%s__lock__${userAddress}`; - const keyLockParamsRecord = (userAddress, txId) => `%s%s%s__lock__${userAddress}__${txId}`; @@ -149,6 +147,7 @@ txs.push({ MAX_LOCK_DURATION.toString(), gwxRewardAddress, BLOCKS_IN_PERIOD.toString(), + LOCK_STEP_BLOCKS.toString(), ].join(separator), } ], diff --git a/ride/boosting.ride b/ride/boosting.ride index c816e5430..18eef80ec 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -6,7 +6,8 @@ # * "%s%s__config__referralsContractAddress": String # * "%s__nextUserNum": Int # * "%s%s__config__factoryAddress": String -# * "%s__config": String ("%s%d%d%d%s%d____________") +# * "%s__config": String +# ("%s%d%d%d%s%d%d______________") # * "%s__lpStakingPoolsContract": String let SEP = "__" @@ -17,12 +18,6 @@ let contractFilename = "boosting.ride" let SCALE18 = 18 let MULT18 = 1_000_000_000_000_000_000 let MULT18BI = MULT18.toBigInt() -let durationMonthsAllowed = [1, 3, 6, 12, 24, 48] - -# 24 * 60 -let blocksInDay = 1440 -# 365 * 24 * 60 / 12 -let blocksInMonth = 43800 func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") func throwErr(msg: String) = msg.wrapErr().throw() @@ -92,6 +87,7 @@ let IdxCfgMinLockDuration = 3 let IdxCfgMaxLockDuration = 4 let IdxCfgMathContract = 5 let IdxCfgBlocksInPeriod = 6 +let IdxCfgLockStepBlocks = 7 func keyConfig() = {"%s__config"} func readConfigArrayOrFail() = this.getStringOrFail(keyConfig()).split(SEP) @@ -102,28 +98,7 @@ let minLockDuration = cfgArray[IdxCfgMinLockDuration].parseInt().valueOrErrorMes let maxLockDuration = cfgArray[IdxCfgMaxLockDuration].parseInt().valueOrErrorMessage(wrapErr("invalid max lock duration")) let mathContract = cfgArray[IdxCfgMathContract].addressFromString().valueOrErrorMessage(wrapErr("invalid math contract address")) let blocksInPeriod = cfgArray[IdxCfgBlocksInPeriod].parseInt().valueOrErrorMessage(wrapErr("invalid blocks in period")) - -func formatConfigS(assetId: String, minLockAmount: String, minLockDuration: String, maxLockDuration: String, mathContract: String) = { - makeString([ - "%s%d%d%d%s", - assetId, # 1 - minLockAmount, # 2 - minLockDuration, # 3 - maxLockDuration, # 4 - mathContract # 5 - ], - SEP) -} - -func formatConfig(assetId: String, minLockAmount: Int, minLockDuration: Int, maxLockDuration: Int, mathContract: String) = { - formatConfigS( - assetId, # 1 - minLockAmount.toString(), # 2 - minLockDuration.toString(), # 3 - maxLockDuration.toString(), # 4 - mathContract # 5 - ) -} +let lockStepBlocks = cfgArray[IdxCfgLockStepBlocks].parseInt().valueOrErrorMessage(wrapErr("invalid lock step blocks")) func getManagerVaultAddressOrThis() = { match keyManagerVaultAddress().getString() { @@ -577,15 +552,13 @@ func internalClaimWxBoost(lpAssetIdStr: String, userAddressStr: String, readOnly (userBoostAvaliableToClaimTotalNew, dataState, debug) } -func lockActions(i: Invocation, durationMonths: Int) = { - let duration = durationMonths * blocksInMonth +func lockActions(i: Invocation, duration: Int) = { let assetIdStr = assetId.toBase58String() if (i.payments.size() != 1) then throwErr("invalid payment - exact one payment must be attached") else let pmt = i.payments[0] let pmtAmount = pmt.amount if (assetId != pmt.assetId.value()) then throwErr("invalid asset is in payment - " + assetIdStr + " is expected") else - if (!durationMonthsAllowed.containsElement(durationMonths)) then throwErr("invalid duration") else let nextUserNumKEY = keyNextUserNum() let userAddress = i.caller @@ -603,6 +576,7 @@ func lockActions(i: Invocation, durationMonths: Int) = { if ((pmtAmount < minLockAmount) && userAddress != lpStakingPoolsContract) then throwErr("amount is less then minLockAmount=" + minLockAmount.toString()) else if (duration < minLockDuration) then throwErr("passed duration is less then minLockDuration=" + minLockDuration.toString()) else if (duration > maxLockDuration) then throwErr("passed duration is greater then maxLockDuration=" + maxLockDuration.toString()) else + if (duration % lockStepBlocks != 0) then throwErr("duration must be multiple of lockStepBlocks=" + lockStepBlocks.toString()) else let gWxAmountStart = fraction(pmtAmount, duration, maxLockDuration) @@ -662,19 +636,6 @@ func getWxWithdrawable(userAddress: Address, txIdOption: ByteVector|Unit) = { wxWithdrawable } -@Callable(i) -func constructor(factoryAddressStr: String, lockAssetIdStr: String, minLockAmount: Int, minDuration: Int, maxDuration: Int, mathContract: String) = { - strict checkCaller = i.mustManager() - - [IntegerEntry(keyNextUserNum(), 0), - StringEntry( - keyConfig(), - formatConfig(lockAssetIdStr, minLockAmount, minDuration, maxDuration, mathContract)), - StringEntry(keyFactoryAddress(), factoryAddressStr) - ] - ++ StatsEntry(0, 0, 0, 0) -} - @Callable(i) func lockRef(duration: Int, referrerAddress: String, signature: ByteVector) = { let (lockActionsResult, gWxAmountStart) = i.lockActions(duration) From 40d614c353aab34ee2d5e32fd4c28bb021ca7ab2 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 21 Aug 2023 13:39:46 +0500 Subject: [PATCH 68/86] remove reentrantInvoke --- ride/boosting.ride | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 18eef80ec..34708c0a2 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -587,7 +587,7 @@ func lockActions(i: Invocation, duration: Int) = { let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) - strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes, userNum], []) + strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes, userNum], []) let arr = if (userIsExisting) then [] else [ IntegerEntry(nextUserNumKEY, userNum + 1), @@ -713,7 +713,7 @@ func unlock(txIdStr: String) = { else let userNum = getString(keyUser2NumMapping(userAddressStr)) .valueOrErrorMessage(wrapErr("invalid user number")).parseIntValue() - strict gwxRewardInv = gwxRewardContract.reentrantInvoke("refreshUserReward", [userAddress.bytes, userNum], []) + strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes, userNum], []) LockParamsEntry(userAddress, txIdOption, userAmount, lockStart, lockDuration, gwxRemaining, wxClaimed + wxWithdrawable) ++ StatsEntry(-wxWithdrawable, 0, 0, 0) From c42952cfc6a7ca1f5e957f2c994273b39e2962af Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 21 Aug 2023 14:28:53 +0500 Subject: [PATCH 69/86] fix existing boosting tests --- test/components/boosting/_hooks.mjs | 4 ++++ .../boosting/claimWxBoostRejectIfUnsupportedLpAsset.mjs | 3 ++- test/components/boosting/contract/boosting.mjs | 4 +++- test/components/boosting/gwxUserInfoREADONLY.mjs | 7 +++---- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/test/components/boosting/_hooks.mjs b/test/components/boosting/_hooks.mjs index c9968e7e5..c1cd03ff5 100644 --- a/test/components/boosting/_hooks.mjs +++ b/test/components/boosting/_hooks.mjs @@ -112,6 +112,8 @@ export const mochaHooks = { }); this.maxLockDuration = 2102400; + this.blocksInPeriod = 1; + this.lockStepBlocks = 1; await boosting.init({ caller: this.accounts.boosting.seed, factoryAddress: this.accounts.factory.addr, @@ -124,6 +126,8 @@ export const mochaHooks = { minLockDuration: this.minDuration, maxLockDuration: this.maxLockDuration, mathContract: this.accounts.gwx.addr, + blocksInPeriod: this.blocksInPeriod, + lockStepBlocks: this.lockStepBlocks, }); const { height } = await api.blocks.fetchHeight(); diff --git a/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAsset.mjs b/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAsset.mjs index b65d4d3af..33dbbaca5 100644 --- a/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAsset.mjs +++ b/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAsset.mjs @@ -52,10 +52,11 @@ describe('boosting: claimWxBoostRejectIfUnsupportedLpAsset.mjs', /** @this {Moch }, this.accounts.factory.seed); await broadcastAndWait(lpAssetTransferTx); + const duration = 10; const { height: lockStartHeight } = await boosting.lock({ dApp: this.accounts.boosting.addr, caller: this.accounts.user0.seed, - duration: 1, + duration, payments: [{ assetId: this.wxAssetId, amount: wxAmount }], }); await waitForHeight(lockStartHeight + 1); diff --git a/test/components/boosting/contract/boosting.mjs b/test/components/boosting/contract/boosting.mjs index 70625ab5d..1cddcc925 100644 --- a/test/components/boosting/contract/boosting.mjs +++ b/test/components/boosting/contract/boosting.mjs @@ -15,6 +15,8 @@ export const boosting = { maxLockDuration, mathContract, nextUserNum = 0, + blocksInPeriod, + lockStepBlocks, }) => { const dataTx = data({ data: [ @@ -25,7 +27,7 @@ export const boosting = { { key: '%s__config', type: 'string', - value: `%s%d%d%d__${lockAssetId}__${minLockAmount}__${minLockDuration}__${maxLockDuration}__${mathContract}`, + value: `%s%d%d%d%s%d%d__${lockAssetId}__${minLockAmount}__${minLockDuration}__${maxLockDuration}__${mathContract}__${blocksInPeriod}__${lockStepBlocks}`, }, { key: '%s__votingEmissionContract', type: 'string', value: votingEmissionAddress }, { key: '%s__managerVaultAddress', type: 'string', value: managerVaultAddress }, diff --git a/test/components/boosting/gwxUserInfoREADONLY.mjs b/test/components/boosting/gwxUserInfoREADONLY.mjs index e16f550ef..deddc3439 100644 --- a/test/components/boosting/gwxUserInfoREADONLY.mjs +++ b/test/components/boosting/gwxUserInfoREADONLY.mjs @@ -43,11 +43,11 @@ describe('boosting: gwxUserInfoREADONLY.mjs', /** @this {MochaSuiteModified} */( }, this.accounts.factory.seed); await broadcastAndWait(lpAssetTransferTx); - const durationInMonths = 1; + const duration = 10; const { height: lockStartHeight } = await boosting.lock({ dApp: this.accounts.boosting.addr, caller: this.accounts.user0.seed, - duration: durationInMonths, + duration, payments: [{ assetId: this.wxAssetId, amount: wxAmount }], }); await waitForHeight(lockStartHeight + 1); @@ -59,9 +59,8 @@ describe('boosting: gwxUserInfoREADONLY.mjs', /** @this {MochaSuiteModified} */( ); const checkData = response.result.value._2.value; - const blocksInMonth = 43800; const expectedGwxAmount = Math.floor(( - wxAmount * durationInMonths * blocksInMonth + wxAmount * duration ) / this.maxLockDuration); // TODO: check all checkData From 55f4e06b8700e18bb1c5cfff6d1ac64b0736aa9c Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 21 Aug 2023 17:54:19 +0500 Subject: [PATCH 70/86] lock test --- test/components/boosting/_hooks.mjs | 212 ++++++++++++++-------------- test/components/boosting/lock.mjs | 80 +++++++++++ 2 files changed, 189 insertions(+), 103 deletions(-) create mode 100644 test/components/boosting/lock.mjs diff --git a/test/components/boosting/_hooks.mjs b/test/components/boosting/_hooks.mjs index c1cd03ff5..dbcc9e628 100644 --- a/test/components/boosting/_hooks.mjs +++ b/test/components/boosting/_hooks.mjs @@ -84,51 +84,27 @@ export const mochaHooks = { this.wavesAssetId = 'WAVES'; - await setScriptFromFile(stakingPath, this.accounts.staking.seed); - await setScriptFromFile(boostingPath, this.accounts.boosting.seed); - await setScriptFromFile(gwxPath, this.accounts.gwx.seed); - await setScriptFromFile(emissionPath, this.accounts.emission.seed); - await setScriptFromFile(referralMockPath, this.accounts.referral.seed); - await setScriptFromFile(votingEmissionPath, this.accounts.votingEmission.seed); - await setScriptFromFile(factoryPath, this.accounts.factory.seed); - await setScriptFromFile(assetsStorePath, this.accounts.store.seed); - await setScriptFromFile(lpPath, this.accounts.lp.seed); - await setScriptFromFile(votingEmissionCandidate, this.accounts.votingEmissionCandidate.seed); - await setScriptFromFile(referralPath, this.accounts.referral.seed); + await Promise.all([ + setScriptFromFile(stakingPath, this.accounts.staking.seed), + setScriptFromFile(boostingPath, this.accounts.boosting.seed), + setScriptFromFile(gwxPath, this.accounts.gwx.seed), + setScriptFromFile(emissionPath, this.accounts.emission.seed), + setScriptFromFile(referralMockPath, this.accounts.referral.seed), + setScriptFromFile(votingEmissionPath, this.accounts.votingEmission.seed), + setScriptFromFile(factoryPath, this.accounts.factory.seed), + setScriptFromFile(assetsStorePath, this.accounts.store.seed), + setScriptFromFile(lpPath, this.accounts.lp.seed), + setScriptFromFile(votingEmissionCandidate, this.accounts.votingEmissionCandidate.seed), + setScriptFromFile(referralPath, this.accounts.referral.seed), + ]); this.minLockAmount = 500000000; this.minDuration = 2; this.maxDuration = 2102400; - await managerVault.init({ - caller: this.accounts.managerVault.seed, - managerPublicKey: this.accounts.manager.publicKey, - }); - - await staking.init({ - caller: this.accounts.staking.seed, - factoryAddress: this.accounts.factory.addr, - votingEmissionAddress: this.accounts.votingEmission.addr, - }); - this.maxLockDuration = 2102400; this.blocksInPeriod = 1; this.lockStepBlocks = 1; - await boosting.init({ - caller: this.accounts.boosting.seed, - factoryAddress: this.accounts.factory.addr, - referralsAddress: this.accounts.referral.addr, - votingEmissionAddress: this.accounts.votingEmission.addr, - lpStakingPoolsAddress: this.accounts.lpStakingPools.addr, - managerVaultAddress: this.accounts.managerVault.addr, - lockAssetId: this.wxAssetId, - minLockAmount: this.minLockAmount, - minLockDuration: this.minDuration, - maxLockDuration: this.maxLockDuration, - mathContract: this.accounts.gwx.addr, - blocksInPeriod: this.blocksInPeriod, - lockStepBlocks: this.lockStepBlocks, - }); const { height } = await api.blocks.fetchHeight(); this.releaseRate = 3805175038; @@ -137,74 +113,102 @@ export const mochaHooks = { this.emissionEndBlock = 4434750; this.emissionDuration = this.emissionEndBlock - this.emissionStartBlock; - await emission.init({ - caller: this.accounts.emission.seed, - factoryAddress: this.accounts.factory.addr, - ratePerBlockMax: this.releaseRateMax, - ratePerBlock: this.releaseRate, - emissionStartBlock: this.emissionStartBlock, - emissionDuration: this.emissionDuration, - wxAssetId: this.wxAssetId, - boostingV2StartHeight: height, - }); - await waitForHeight(height + 1); - - await factory.init({ - caller: this.accounts.factory.seed, - stakingAddress: this.accounts.staking.addr, - boostingAddress: this.accounts.boosting.addr, - emissionAddress: this.accounts.emission.addr, - gwxAddress: this.accounts.gwx.addr, - votingEmissionAddress: this.accounts.votingEmission.addr, - }); - - await gwx.init({ - caller: this.accounts.gwx.seed, - referralAddress: this.accounts.referral.addr, - wxAssetId: this.wxAssetId, - matcherPacemakerAddress: '', - boostingContractAddress: this.accounts.boosting.addr, - gwxRewardEmissionPartStartHeight: 1, - emissionContractAddress: this.accounts.emission.addr, - }); - - await assetsStore.init({ - caller: this.accounts.store.seed, - factorySeed: this.accounts.factory.seed, - labels: 'COMMUNITY_VERIFIED__GATEWAY__STABLECOIN__STAKING_LP__3RD_PARTY__ALGO_LP__LAMBO_LP__POOLS_LP__WX__PEPE', - }); - this.epochLength = 7; - await votingEmission.init({ - dApp: this.accounts.votingEmission.addr, - caller: this.accounts.votingEmission.seed, - factoryAddress: this.accounts.factory.addr, - votingEmissionCandidateAddress: this.accounts.votingEmissionCandidate.addr, - boostingAddress: this.accounts.boosting.addr, - stakingAddress: this.accounts.staking.addr, - epochLength: this.epochLength, - }); - - await factory.setWxEmissionPoolLabel({ - dApp: this.accounts.factory.addr, - caller: this.accounts.factory.seed, - amountAssetId: this.wxAssetId, - priceAssetId: this.wavesAssetId, - }); - - await votingEmission.create({ - dApp: this.accounts.votingEmission.addr, - caller: this.accounts.votingEmission.seed, - amountAssetId: this.wxAssetId, - priceAssetId: this.wavesAssetId, - }); - - await votingEmission.updateEpochUiKey({ - caller: this.accounts.votingEmission.seed, - epochUiKey: height + 10, - epochStartHeight: height, - }); + await Promise.all([ + managerVault.init({ + caller: this.accounts.managerVault.seed, + managerPublicKey: this.accounts.manager.publicKey, + }), + + staking.init({ + caller: this.accounts.staking.seed, + factoryAddress: this.accounts.factory.addr, + votingEmissionAddress: this.accounts.votingEmission.addr, + }), + + boosting.init({ + caller: this.accounts.boosting.seed, + factoryAddress: this.accounts.factory.addr, + referralsAddress: this.accounts.referral.addr, + votingEmissionAddress: this.accounts.votingEmission.addr, + lpStakingPoolsAddress: this.accounts.lpStakingPools.addr, + managerVaultAddress: this.accounts.managerVault.addr, + lockAssetId: this.wxAssetId, + minLockAmount: this.minLockAmount, + minLockDuration: this.minDuration, + maxLockDuration: this.maxLockDuration, + mathContract: this.accounts.gwx.addr, + blocksInPeriod: this.blocksInPeriod, + lockStepBlocks: this.lockStepBlocks, + }), + + emission.init({ + caller: this.accounts.emission.seed, + factoryAddress: this.accounts.factory.addr, + ratePerBlockMax: this.releaseRateMax, + ratePerBlock: this.releaseRate, + emissionStartBlock: this.emissionStartBlock, + emissionDuration: this.emissionDuration, + wxAssetId: this.wxAssetId, + boostingV2StartHeight: height, + }), + + factory.init({ + caller: this.accounts.factory.seed, + stakingAddress: this.accounts.staking.addr, + boostingAddress: this.accounts.boosting.addr, + emissionAddress: this.accounts.emission.addr, + gwxAddress: this.accounts.gwx.addr, + votingEmissionAddress: this.accounts.votingEmission.addr, + }), + + gwx.init({ + caller: this.accounts.gwx.seed, + referralAddress: this.accounts.referral.addr, + wxAssetId: this.wxAssetId, + matcherPacemakerAddress: '', + boostingContractAddress: this.accounts.boosting.addr, + gwxRewardEmissionPartStartHeight: 1, + emissionContractAddress: this.accounts.emission.addr, + }), + + assetsStore.init({ + caller: this.accounts.store.seed, + factorySeed: this.accounts.factory.seed, + labels: 'COMMUNITY_VERIFIED__GATEWAY__STABLECOIN__STAKING_LP__3RD_PARTY__ALGO_LP__LAMBO_LP__POOLS_LP__WX__PEPE', + }), + + votingEmission.init({ + dApp: this.accounts.votingEmission.addr, + caller: this.accounts.votingEmission.seed, + factoryAddress: this.accounts.factory.addr, + votingEmissionCandidateAddress: this.accounts.votingEmissionCandidate.addr, + boostingAddress: this.accounts.boosting.addr, + stakingAddress: this.accounts.staking.addr, + epochLength: this.epochLength, + }), + + factory.setWxEmissionPoolLabel({ + dApp: this.accounts.factory.addr, + caller: this.accounts.factory.seed, + amountAssetId: this.wxAssetId, + priceAssetId: this.wavesAssetId, + }), + + votingEmission.create({ + dApp: this.accounts.votingEmission.addr, + caller: this.accounts.votingEmission.seed, + amountAssetId: this.wxAssetId, + priceAssetId: this.wavesAssetId, + }), + + votingEmission.updateEpochUiKey({ + caller: this.accounts.votingEmission.seed, + epochUiKey: height + 10, + epochStartHeight: height, + }), + ]); ({ lpAssetId: this.lpAssetId } = await factory.createPool({ amountAssetId: this.wxAssetId, @@ -221,6 +225,8 @@ export const mochaHooks = { poolWeight: 1000, }); + await waitForHeight(height + 1); + const accountsInfo = Object.entries(this.accounts) .map(([name, { seed, addr }]) => [name, seed, privateKey(seed), addr]); console.log(table(accountsInfo, { diff --git a/test/components/boosting/lock.mjs b/test/components/boosting/lock.mjs new file mode 100644 index 000000000..6a81037f7 --- /dev/null +++ b/test/components/boosting/lock.mjs @@ -0,0 +1,80 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { + transfer, + invokeScript, +} from '@waves/waves-transactions'; + +import { broadcastAndWait, chainId, separator } from '../../utils/api.mjs'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +describe('boosting: lock.mjs', /** @this {MochaSuiteModified} */() => { + it( + 'should successfully lock', + async function () { + const boosting = this.accounts.boosting.addr; + const lockDuration = 3; + + const lockWxAmount = 1e3 * 1e8; + + await broadcastAndWait(transfer({ + recipient: this.accounts.user0.addr, + amount: lockWxAmount, + assetId: this.wxAssetId, + additionalFee: 4e5, + }, this.accounts.emission.seed)); + + const { stateChanges, id: lockTxId } = await broadcastAndWait(invokeScript({ + dApp: boosting, + call: { + function: 'lock', + args: [ + { type: 'integer', value: lockDuration }, + ], + }, + payment: [ + { assetId: this.wxAssetId, amount: lockWxAmount }, + ], + chainId, + }, this.accounts.user0.seed)); + + const keyLock = (userAddress, txId) => ['%s%s%s', 'lock', userAddress, txId].join(separator); + const parseLockParams = (s) => { + const [ + meta, + wxAmount, + startHeight, + duration, + lastUpdateTimestamp, + gwxAmount, + wxClaimed, + ] = s.split(separator); + + return { + meta, + wxAmount: parseInt(wxAmount, 10), + startHeight: parseInt(startHeight, 10), + duration: parseInt(duration, 10), + gwxAmount: parseInt(gwxAmount, 10), + wxClaimed: parseInt(wxClaimed, 10), + lastUpdateTimestamp: parseInt(lastUpdateTimestamp, 10), + }; + }; + const boostingDataChanges = Object.fromEntries( + stateChanges.data.map(({ key, value }) => [key, value]), + ); + + const lockKey = keyLock(this.accounts.user0.addr, lockTxId); + const lockParams = parseLockParams( + boostingDataChanges[lockKey], + ); + + const expectedGwxAmount = Math.floor((lockWxAmount * lockDuration) / this.maxLockDuration); + expect(lockParams.wxAmount).to.equal(lockWxAmount); + expect(lockParams.gwxAmount).to.equal(expectedGwxAmount); + }, + ); +}); From c349b88428dd717130dd6aae863eb92f8ff83e84 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 21 Aug 2023 19:50:31 +0500 Subject: [PATCH 71/86] lock test fix, unlock test --- ride/boosting.ride | 2 +- test/components/boosting/_hooks.mjs | 11 ++ .../components/boosting/contract/boosting.mjs | 37 +++++- .../boosting/contract/votingEmission.mjs | 2 + .../boosting/contract/votingEmissionRate.mjs | 24 ++++ test/components/boosting/lock.mjs | 112 +++++++----------- test/components/boosting/unlock.mjs | 69 +++++++++++ 7 files changed, 180 insertions(+), 77 deletions(-) create mode 100644 test/components/boosting/contract/votingEmissionRate.mjs create mode 100644 test/components/boosting/unlock.mjs diff --git a/ride/boosting.ride b/ride/boosting.ride index 34708c0a2..3cc751480 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -702,7 +702,7 @@ func unlock(txIdStr: String) = { let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") let lockedGwxAmount = getLockedGwxAmount(userAddress) - if (userAmount <= 0) then throwErr("nothing to unlock") else + if (wxWithdrawable <= 0) then throwErr("nothing to unlock") else let gwxAmountTotal = getGwxAmountTotal() let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) diff --git a/test/components/boosting/_hooks.mjs b/test/components/boosting/_hooks.mjs index dbcc9e628..181569a35 100644 --- a/test/components/boosting/_hooks.mjs +++ b/test/components/boosting/_hooks.mjs @@ -19,6 +19,7 @@ import { assetsStore } from './contract/assetsStore.mjs'; import { gwx } from './contract/gwx.mjs'; import { votingEmission } from './contract/votingEmission.mjs'; import { managerVault } from './contract/managerVault.mjs'; +import { votingEmissionRate } from './contract/votingEmissionRate.mjs'; const nonceLength = 3; @@ -35,6 +36,7 @@ const assetsStorePath = format({ dir: ridePath, base: 'assets_store.ride' }); const lpPath = format({ dir: ridePath, base: 'lp.ride' }); const votingEmissionCandidate = format({ dir: ridePath, base: 'voting_emission_candidate.ride' }); const referralPath = format({ dir: ridePath, base: 'referral.ride' }); +const votingEmissionRatePath = format({ dir: ridePath, base: 'voting_emission_rate.ride' }); export const mochaHooks = { async beforeAll() { @@ -50,6 +52,7 @@ export const mochaHooks = { 'lp', 'factory', 'votingEmission', + 'votingEmisisonRate', 'referral', 'votingEmissionCandidate', 'manager', @@ -96,6 +99,7 @@ export const mochaHooks = { setScriptFromFile(lpPath, this.accounts.lp.seed), setScriptFromFile(votingEmissionCandidate, this.accounts.votingEmissionCandidate.seed), setScriptFromFile(referralPath, this.accounts.referral.seed), + setScriptFromFile(votingEmissionRatePath, this.accounts.votingEmisisonRate.seed), ]); this.minLockAmount = 500000000; @@ -187,6 +191,7 @@ export const mochaHooks = { boostingAddress: this.accounts.boosting.addr, stakingAddress: this.accounts.staking.addr, epochLength: this.epochLength, + votingEmissionRateAddress: this.accounts.votingEmisisonRate.addr, }), factory.setWxEmissionPoolLabel({ @@ -208,6 +213,12 @@ export const mochaHooks = { epochUiKey: height + 10, epochStartHeight: height, }), + + votingEmissionRate.init({ + dApp: this.accounts.votingEmisisonRate.addr, + caller: this.accounts.votingEmisisonRate.seed, + startHeight: height, + }), ]); ({ lpAssetId: this.lpAssetId } = await factory.createPool({ diff --git a/test/components/boosting/contract/boosting.mjs b/test/components/boosting/contract/boosting.mjs index 1cddcc925..4ee2e1faf 100644 --- a/test/components/boosting/contract/boosting.mjs +++ b/test/components/boosting/contract/boosting.mjs @@ -1,5 +1,30 @@ import { data, invokeScript } from '@waves/waves-transactions'; -import { broadcastAndWait, chainId } from '../../../utils/api.mjs'; +import { broadcastAndWait, chainId, separator } from '../../../utils/api.mjs'; + +export const keyLock = (userAddress, txId) => ['%s%s%s', 'lock', userAddress, txId].join(separator); +export const keyUserGwxAmountTotal = (userAddress) => ['%s%s', 'gwxAmountTotal', userAddress].join(separator); + +export const parseLockParams = (s) => { + const [ + meta, + wxAmount, + startHeight, + duration, + lastUpdateTimestamp, + gwxAmount, + wxClaimed, + ] = s.split(separator); + + return { + meta, + wxAmount: parseInt(wxAmount, 10), + startHeight: parseInt(startHeight, 10), + duration: parseInt(duration, 10), + gwxAmount: parseInt(gwxAmount, 10), + wxClaimed: parseInt(wxClaimed, 10), + lastUpdateTimestamp: parseInt(lastUpdateTimestamp, 10), + }; +}; export const boosting = { init: async ({ @@ -60,19 +85,19 @@ export const boosting = { return broadcastAndWait(invokeTx); }, - increaseLock: async ({ - dApp, caller, deltaDuration, payments = [], + unlock: async ({ + dApp, caller, txId, }) => { const invokeTx = invokeScript( { dApp, call: { - function: 'increaseLock', + function: 'unlock', args: [ - { type: 'integer', value: deltaDuration }, + { type: 'string', value: txId }, ], }, - payment: payments, + payment: [], additionalFee: 4e5, chainId, }, diff --git a/test/components/boosting/contract/votingEmission.mjs b/test/components/boosting/contract/votingEmission.mjs index 5e05ac07a..6c85782bc 100644 --- a/test/components/boosting/contract/votingEmission.mjs +++ b/test/components/boosting/contract/votingEmission.mjs @@ -10,6 +10,7 @@ export const votingEmission = { boostingAddress, stakingAddress, epochLength, + votingEmissionRateAddress, }) => { const dataTx = data( { @@ -20,6 +21,7 @@ export const votingEmission = { { key: '%s__boostingContract', type: 'string', value: boostingAddress }, { key: '%s__stakingContract', type: 'string', value: stakingAddress }, { key: '%s__epochLength', type: 'integer', value: epochLength }, + { key: '%s__votingEmissionRateContract', type: 'string', value: votingEmissionRateAddress }, ], additionalFee: 4e5, chainId, diff --git a/test/components/boosting/contract/votingEmissionRate.mjs b/test/components/boosting/contract/votingEmissionRate.mjs new file mode 100644 index 000000000..625fcab44 --- /dev/null +++ b/test/components/boosting/contract/votingEmissionRate.mjs @@ -0,0 +1,24 @@ +import { data } from '@waves/waves-transactions'; +import { broadcastAndWait, chainId } from '../../../utils/api.mjs'; + +export const votingEmissionRate = { + init: async ({ + dApp, + caller, + startHeight, + }) => { + const dataTx = data( + { + dApp, + data: [ + { key: '%s__startHeight', type: 'integer', value: startHeight }, + ], + additionalFee: 4e5, + chainId, + }, + caller, + ); + + return broadcastAndWait(dataTx); + }, +}; diff --git a/test/components/boosting/lock.mjs b/test/components/boosting/lock.mjs index 6a81037f7..813a99954 100644 --- a/test/components/boosting/lock.mjs +++ b/test/components/boosting/lock.mjs @@ -1,80 +1,52 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import { - transfer, - invokeScript, -} from '@waves/waves-transactions'; +import { transfer } from '@waves/waves-transactions'; -import { broadcastAndWait, chainId, separator } from '../../utils/api.mjs'; +import { broadcastAndWait } from '../../utils/api.mjs'; +import { + boosting, parseLockParams, keyLock, keyUserGwxAmountTotal, +} from './contract/boosting.mjs'; chai.use(chaiAsPromised); const { expect } = chai; describe('boosting: lock.mjs', /** @this {MochaSuiteModified} */() => { - it( - 'should successfully lock', - async function () { - const boosting = this.accounts.boosting.addr; - const lockDuration = 3; - - const lockWxAmount = 1e3 * 1e8; - - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: lockWxAmount, - assetId: this.wxAssetId, - additionalFee: 4e5, - }, this.accounts.emission.seed)); - - const { stateChanges, id: lockTxId } = await broadcastAndWait(invokeScript({ - dApp: boosting, - call: { - function: 'lock', - args: [ - { type: 'integer', value: lockDuration }, - ], - }, - payment: [ - { assetId: this.wxAssetId, amount: lockWxAmount }, - ], - chainId, - }, this.accounts.user0.seed)); - - const keyLock = (userAddress, txId) => ['%s%s%s', 'lock', userAddress, txId].join(separator); - const parseLockParams = (s) => { - const [ - meta, - wxAmount, - startHeight, - duration, - lastUpdateTimestamp, - gwxAmount, - wxClaimed, - ] = s.split(separator); - - return { - meta, - wxAmount: parseInt(wxAmount, 10), - startHeight: parseInt(startHeight, 10), - duration: parseInt(duration, 10), - gwxAmount: parseInt(gwxAmount, 10), - wxClaimed: parseInt(wxClaimed, 10), - lastUpdateTimestamp: parseInt(lastUpdateTimestamp, 10), - }; - }; - const boostingDataChanges = Object.fromEntries( - stateChanges.data.map(({ key, value }) => [key, value]), - ); - - const lockKey = keyLock(this.accounts.user0.addr, lockTxId); - const lockParams = parseLockParams( - boostingDataChanges[lockKey], - ); - - const expectedGwxAmount = Math.floor((lockWxAmount * lockDuration) / this.maxLockDuration); - expect(lockParams.wxAmount).to.equal(lockWxAmount); - expect(lockParams.gwxAmount).to.equal(expectedGwxAmount); - }, - ); + it('should successfully lock', async function () { + const lockDuration = 3; + + const lockWxAmount = 1e3 * 1e8; + + await broadcastAndWait(transfer({ + recipient: this.accounts.user0.addr, + amount: lockWxAmount, + assetId: this.wxAssetId, + additionalFee: 4e5, + }, this.accounts.emission.seed)); + + const { stateChanges, id: lockTxId } = await boosting.lock({ + dApp: this.accounts.boosting.addr, + caller: this.accounts.user0.seed, + duration: lockDuration, + payments: [ + { assetId: this.wxAssetId, amount: lockWxAmount }, + ], + }); + + const boostingDataChanges = Object.fromEntries( + stateChanges.data.map(({ key, value }) => [key, value]), + ); + + const lockKey = keyLock(this.accounts.user0.addr, lockTxId); + const lockParams = parseLockParams( + boostingDataChanges[lockKey], + ); + + const expectedGwxAmount = Math.floor((lockWxAmount * lockDuration) / this.maxLockDuration); + expect(lockParams.wxAmount).to.equal(lockWxAmount); + expect(lockParams.gwxAmount).to.equal(expectedGwxAmount); + expect( + boostingDataChanges[keyUserGwxAmountTotal(this.accounts.user0.addr)], + ).to.equal(expectedGwxAmount); + }); }); diff --git a/test/components/boosting/unlock.mjs b/test/components/boosting/unlock.mjs new file mode 100644 index 000000000..9d3b1abeb --- /dev/null +++ b/test/components/boosting/unlock.mjs @@ -0,0 +1,69 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { + transfer, +} from '@waves/waves-transactions'; + +import { + broadcastAndWait, chainId, waitNBlocks, api, +} from '../../utils/api.mjs'; +import { boosting, parseLockParams, keyLock } from './contract/boosting.mjs'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { + const lockDuration = 3; + const lockWxAmount = 1e3 * 1e8; + let lockTxId; + let lockHeight; + + before(async function () { + await broadcastAndWait(transfer({ + recipient: this.accounts.user0.addr, + amount: lockWxAmount, + assetId: this.wxAssetId, + additionalFee: 4e5, + }, this.accounts.emission.seed)); + + ({ id: lockTxId, height: lockHeight } = await boosting.lock({ + dApp: this.accounts.boosting.addr, + caller: this.accounts.user0.seed, + duration: lockDuration, + payments: [ + { assetId: this.wxAssetId, amount: lockWxAmount }, + ], + chainId, + })); + }); + + it('should successfully unlock', async function () { + const { height: currentHeight } = await api.blocks.fetchHeight(); + let heightDiff = currentHeight - lockHeight; + if (heightDiff === 0) { + await waitNBlocks(1); + heightDiff += 1; + } + const { stateChanges, height: unlockHeight } = await boosting.unlock({ + dApp: this.accounts.boosting.addr, + caller: this.accounts.user0.seed, + txId: lockTxId, + }); + const boostingDataChanges = Object.fromEntries( + stateChanges.data.map(({ key, value }) => [key, value]), + ); + + const lockKey = keyLock(this.accounts.user0.addr, lockTxId); + const lockParams = parseLockParams( + boostingDataChanges[lockKey], + ); + + const t = Math.floor((unlockHeight - lockHeight) / this.blocksInPeriod); + const exponent = (t * 8 * this.blocksInPeriod) / lockDuration; + // if height > lockEnd then userAmount + const wxWithdrawable = Math.floor(lockWxAmount * (1 - 0.5 ** exponent)); + + expect(lockParams.wxClaimed).to.equal(wxWithdrawable); + }); +}); From cd4df150e13a55b2369993b146aee439c8caf835 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:55:41 +0500 Subject: [PATCH 72/86] unlock gwx burned check --- test/components/boosting/unlock.mjs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/test/components/boosting/unlock.mjs b/test/components/boosting/unlock.mjs index 9d3b1abeb..9e0070282 100644 --- a/test/components/boosting/unlock.mjs +++ b/test/components/boosting/unlock.mjs @@ -18,6 +18,7 @@ describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { const lockWxAmount = 1e3 * 1e8; let lockTxId; let lockHeight; + let lockParamsPrev; before(async function () { await broadcastAndWait(transfer({ @@ -27,7 +28,8 @@ describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { additionalFee: 4e5, }, this.accounts.emission.seed)); - ({ id: lockTxId, height: lockHeight } = await boosting.lock({ + let stateChanges; + ({ id: lockTxId, height: lockHeight, stateChanges } = await boosting.lock({ dApp: this.accounts.boosting.addr, caller: this.accounts.user0.seed, duration: lockDuration, @@ -36,6 +38,15 @@ describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { ], chainId, })); + + const boostingDataChanges = Object.fromEntries( + stateChanges.data.map(({ key, value }) => [key, value]), + ); + + const lockKey = keyLock(this.accounts.user0.addr, lockTxId); + lockParamsPrev = parseLockParams( + boostingDataChanges[lockKey], + ); }); it('should successfully unlock', async function () { @@ -63,7 +74,16 @@ describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { const exponent = (t * 8 * this.blocksInPeriod) / lockDuration; // if height > lockEnd then userAmount const wxWithdrawable = Math.floor(lockWxAmount * (1 - 0.5 ** exponent)); + const gwxAmountStart = Math.floor((lockWxAmount * lockDuration) / this.maxLockDuration); + const gwxAmountPrev = lockParamsPrev.gwxAmount; + const gwxBurned = Math.min( + Math.floor( + (t * this.blocksInPeriod * gwxAmountStart) / this.maxLockDuration, + ), + gwxAmountPrev, + ); - expect(lockParams.wxClaimed).to.equal(wxWithdrawable); + expect(lockParams.wxClaimed).to.equal(wxWithdrawable, 'wxClaimed'); + expect(lockParams.gwxAmount).to.equal(gwxAmountPrev - gwxBurned, 'gwxAmount'); }); }); From f62f3409f7eddf94d966b8c627703407b4ea8b24 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:09:09 +0500 Subject: [PATCH 73/86] decay constant --- ride/boosting.ride | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 3cc751480..40ac60f65 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -18,6 +18,7 @@ let contractFilename = "boosting.ride" let SCALE18 = 18 let MULT18 = 1_000_000_000_000_000_000 let MULT18BI = MULT18.toBigInt() +let DECAY_CONSTANT = 8 func wrapErr(msg: String) = [contractFilename, ": ", msg].makeString("") func throwErr(msg: String) = msg.wrapErr().throw() @@ -620,7 +621,7 @@ func getWxWithdrawable(userAddress: Address, txIdOption: ByteVector|Unit) = { let exponent = fraction( t.toBigInt(), - { 8 * blocksInPeriod }.toBigInt() * MULT18BI, + { DECAY_CONSTANT * blocksInPeriod }.toBigInt() * MULT18BI, lockDuration.toBigInt() ) let wxWithdrawable = if (height > lockEnd) then { From e43f0c92fba6fa9e2253ac1446189de0212d18ee Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:25:55 +0500 Subject: [PATCH 74/86] tests update --- test/components/boosting/_hooks.mjs | 5 +- .../components/boosting/contract/boosting.mjs | 74 ++++++++++++++----- test/components/boosting/lock.mjs | 6 +- test/components/boosting/unlock.mjs | 26 +++---- 4 files changed, 78 insertions(+), 33 deletions(-) diff --git a/test/components/boosting/_hooks.mjs b/test/components/boosting/_hooks.mjs index 181569a35..137e63c37 100644 --- a/test/components/boosting/_hooks.mjs +++ b/test/components/boosting/_hooks.mjs @@ -119,6 +119,10 @@ export const mochaHooks = { this.epochLength = 7; + boosting.seed = this.accounts.boosting.seed; + boosting.maxLockDuration = this.maxLockDuration; + boosting.blocksInPeriod = this.blocksInPeriod; + await Promise.all([ managerVault.init({ caller: this.accounts.managerVault.seed, @@ -132,7 +136,6 @@ export const mochaHooks = { }), boosting.init({ - caller: this.accounts.boosting.seed, factoryAddress: this.accounts.factory.addr, referralsAddress: this.accounts.referral.addr, votingEmissionAddress: this.accounts.votingEmission.addr, diff --git a/test/components/boosting/contract/boosting.mjs b/test/components/boosting/contract/boosting.mjs index 4ee2e1faf..fb1dc113a 100644 --- a/test/components/boosting/contract/boosting.mjs +++ b/test/components/boosting/contract/boosting.mjs @@ -1,4 +1,5 @@ import { data, invokeScript } from '@waves/waves-transactions'; +import wc from '@waves/ts-lib-crypto'; import { broadcastAndWait, chainId, separator } from '../../../utils/api.mjs'; export const keyLock = (userAddress, txId) => ['%s%s%s', 'lock', userAddress, txId].join(separator); @@ -26,9 +27,46 @@ export const parseLockParams = (s) => { }; }; -export const boosting = { - init: async ({ - caller, +const decayConstant = 8; +class Boosting { + maxLockDuration = 0; + + blocksInPeriod = 0; + + seed = ''; + + address() { + return wc.address(this.seed, chainId); + } + + calcGwxAmountStart({ wxAmount, duration }) { + return Math.floor((wxAmount * duration) / this.maxLockDuration); + } + + calcWxWithdrawable({ + lockWxAmount, lockDuration, passedPeriods, + }) { + const exponent = (passedPeriods * decayConstant * this.blocksInPeriod) / lockDuration; + // TODO: if height > lockEnd then userAmount + const wxWithdrawable = Math.floor(lockWxAmount * (1 - 0.5 ** exponent)); + + return wxWithdrawable; + } + + calcGwxAmountBurned({ + gwxAmountStart, gwxAmountPrev, passedPeriods, + }) { + const gwxBurned = Math.min( + Math.floor( + (passedPeriods * this.blocksInPeriod * gwxAmountStart) / this.maxLockDuration, + ), + gwxAmountPrev, + ); + + return gwxBurned; + } + + async init({ factoryAddress, referralsAddress, votingEmissionAddress, @@ -42,7 +80,7 @@ export const boosting = { nextUserNum = 0, blocksInPeriod, lockStepBlocks, - }) => { + }) { const dataTx = data({ data: [ { key: '%s%s__config__factoryAddress', type: 'string', value: factoryAddress }, @@ -59,17 +97,17 @@ export const boosting = { ], additionalFee: 4e5, chainId, - }, caller); + }, this.seed); return broadcastAndWait(dataTx); - }, + } - lock: async ({ - dApp, caller, duration, payments = [], - }) => { + async lock({ + caller, duration, payments = [], + }) { const invokeTx = invokeScript( { - dApp, + dApp: this.address(), call: { function: 'lock', args: [ @@ -83,14 +121,14 @@ export const boosting = { caller, ); return broadcastAndWait(invokeTx); - }, + } - unlock: async ({ - dApp, caller, txId, - }) => { + async unlock({ + caller, txId, + }) { const invokeTx = invokeScript( { - dApp, + dApp: this.address(), call: { function: 'unlock', args: [ @@ -104,5 +142,7 @@ export const boosting = { caller, ); return broadcastAndWait(invokeTx); - }, -}; + } +} + +export const boosting = new Boosting(); diff --git a/test/components/boosting/lock.mjs b/test/components/boosting/lock.mjs index 813a99954..7aa8c7e8e 100644 --- a/test/components/boosting/lock.mjs +++ b/test/components/boosting/lock.mjs @@ -25,7 +25,6 @@ describe('boosting: lock.mjs', /** @this {MochaSuiteModified} */() => { }, this.accounts.emission.seed)); const { stateChanges, id: lockTxId } = await boosting.lock({ - dApp: this.accounts.boosting.addr, caller: this.accounts.user0.seed, duration: lockDuration, payments: [ @@ -42,7 +41,10 @@ describe('boosting: lock.mjs', /** @this {MochaSuiteModified} */() => { boostingDataChanges[lockKey], ); - const expectedGwxAmount = Math.floor((lockWxAmount * lockDuration) / this.maxLockDuration); + const expectedGwxAmount = boosting.calcGwxAmountStart({ + wxAmount: lockWxAmount, + duration: lockDuration, + }); expect(lockParams.wxAmount).to.equal(lockWxAmount); expect(lockParams.gwxAmount).to.equal(expectedGwxAmount); expect( diff --git a/test/components/boosting/unlock.mjs b/test/components/boosting/unlock.mjs index 9e0070282..231ec0408 100644 --- a/test/components/boosting/unlock.mjs +++ b/test/components/boosting/unlock.mjs @@ -30,7 +30,6 @@ describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { let stateChanges; ({ id: lockTxId, height: lockHeight, stateChanges } = await boosting.lock({ - dApp: this.accounts.boosting.addr, caller: this.accounts.user0.seed, duration: lockDuration, payments: [ @@ -57,7 +56,6 @@ describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { heightDiff += 1; } const { stateChanges, height: unlockHeight } = await boosting.unlock({ - dApp: this.accounts.boosting.addr, caller: this.accounts.user0.seed, txId: lockTxId, }); @@ -70,18 +68,20 @@ describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { boostingDataChanges[lockKey], ); - const t = Math.floor((unlockHeight - lockHeight) / this.blocksInPeriod); - const exponent = (t * 8 * this.blocksInPeriod) / lockDuration; - // if height > lockEnd then userAmount - const wxWithdrawable = Math.floor(lockWxAmount * (1 - 0.5 ** exponent)); - const gwxAmountStart = Math.floor((lockWxAmount * lockDuration) / this.maxLockDuration); + const passedPeriods = Math.floor((unlockHeight - lockHeight) / this.blocksInPeriod); + const wxWithdrawable = boosting.calcWxWithdrawable({ + lockWxAmount, + lockDuration, + passedPeriods, + }); + const gwxAmountStart = boosting.calcGwxAmountStart({ + wxAmount: lockWxAmount, + duration: lockDuration, + }); const gwxAmountPrev = lockParamsPrev.gwxAmount; - const gwxBurned = Math.min( - Math.floor( - (t * this.blocksInPeriod * gwxAmountStart) / this.maxLockDuration, - ), - gwxAmountPrev, - ); + const gwxBurned = boosting.calcGwxAmountBurned({ + gwxAmountStart, gwxAmountPrev, passedPeriods, + }); expect(lockParams.wxClaimed).to.equal(wxWithdrawable, 'wxClaimed'); expect(lockParams.gwxAmount).to.equal(gwxAmountPrev - gwxBurned, 'gwxAmount'); From 6c1b5649ca1543691435986823a1689d0763626c Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 22 Aug 2023 19:26:27 +0500 Subject: [PATCH 75/86] gwx claim reward test draft --- ride/gwx_reward.ride | 4 +- test/components/boosting/_hooks.mjs | 7 +-- test/components/boosting/claimReward.mjs | 58 +++++++++++++++++++ .../components/boosting/contract/emission.mjs | 6 ++ test/components/boosting/contract/gwx.mjs | 13 ++++- .../{refferal.ride => referral.mock.ride} | 14 +++++ 6 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 test/components/boosting/claimReward.mjs rename test/components/boosting/mock/{refferal.ride => referral.mock.ride} (54%) diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index ff2b38c83..d7ee61755 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -391,11 +391,13 @@ func claimReward() = { referralsContractAddressOrFail.invoke("incUnclaimed", [referralProgramName, userAddress, referrerReward, referralReward], []) } + strict emit = emissionContract.invoke("emit", [amount], []) + strict claimedReferral = referralsContractAddressOrFail.invoke("claim", [referralProgramName], []).exactAs[Int] let totalAmount = amount + claimedReferral ( [ - ScriptTransfer(i.caller, amount, cfgArray[IdxCfgAssetId].fromBase58String()), + ScriptTransfer(i.caller, amount, wxAssetId), HistoryEntry("claim", userAddressStr, totalAmount, i) ] ++ actions, totalAmount diff --git a/test/components/boosting/_hooks.mjs b/test/components/boosting/_hooks.mjs index 137e63c37..ec48adffd 100644 --- a/test/components/boosting/_hooks.mjs +++ b/test/components/boosting/_hooks.mjs @@ -24,7 +24,7 @@ import { votingEmissionRate } from './contract/votingEmissionRate.mjs'; const nonceLength = 3; const ridePath = '../ride'; -const mockPath = './components/staking_boosting/mock'; +const mockPath = './components/boosting/mock'; const stakingPath = format({ dir: ridePath, base: 'staking.ride' }); const boostingPath = format({ dir: ridePath, base: 'boosting.ride' }); const gwxPath = format({ dir: ridePath, base: 'gwx_reward.ride' }); @@ -35,7 +35,6 @@ const factoryPath = format({ dir: ridePath, base: 'factory_v2.ride' }); const assetsStorePath = format({ dir: ridePath, base: 'assets_store.ride' }); const lpPath = format({ dir: ridePath, base: 'lp.ride' }); const votingEmissionCandidate = format({ dir: ridePath, base: 'voting_emission_candidate.ride' }); -const referralPath = format({ dir: ridePath, base: 'referral.ride' }); const votingEmissionRatePath = format({ dir: ridePath, base: 'voting_emission_rate.ride' }); export const mochaHooks = { @@ -57,7 +56,6 @@ export const mochaHooks = { 'votingEmissionCandidate', 'manager', 'managerVault', - 'referral', 'referrer', 'lpStakingPools', ]; @@ -98,7 +96,6 @@ export const mochaHooks = { setScriptFromFile(assetsStorePath, this.accounts.store.seed), setScriptFromFile(lpPath, this.accounts.lp.seed), setScriptFromFile(votingEmissionCandidate, this.accounts.votingEmissionCandidate.seed), - setScriptFromFile(referralPath, this.accounts.referral.seed), setScriptFromFile(votingEmissionRatePath, this.accounts.votingEmisisonRate.seed), ]); @@ -116,6 +113,7 @@ export const mochaHooks = { this.emissionStartBlock = 1806750; this.emissionEndBlock = 4434750; this.emissionDuration = this.emissionEndBlock - this.emissionStartBlock; + this.gwxHoldersReward = 0.2 * 1e8; this.epochLength = 7; @@ -159,6 +157,7 @@ export const mochaHooks = { emissionDuration: this.emissionDuration, wxAssetId: this.wxAssetId, boostingV2StartHeight: height, + gwxHoldersReward: this.gwxHoldersReward, }), factory.init({ diff --git a/test/components/boosting/claimReward.mjs b/test/components/boosting/claimReward.mjs new file mode 100644 index 000000000..8f368e12e --- /dev/null +++ b/test/components/boosting/claimReward.mjs @@ -0,0 +1,58 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { + transfer, +} from '@waves/waves-transactions'; + +import { + broadcastAndWait, chainId, waitNBlocks, api, +} from '../../utils/api.mjs'; +import { boosting, parseLockParams, keyLock } from './contract/boosting.mjs'; +import { gwx } from './contract/gwx.mjs'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +describe('boosting: claimReward.mjs', /** @this {MochaSuiteModified} */() => { + const lockDuration = 3; + const lockWxAmount = 1e3 * 1e8; + let lockTxId; + + before(async function () { + await broadcastAndWait(transfer({ + recipient: this.accounts.user0.addr, + amount: lockWxAmount, + assetId: this.wxAssetId, + additionalFee: 4e5, + }, this.accounts.emission.seed)); + + let stateChanges; + ({ id: lockTxId, height: lockHeight, stateChanges } = await boosting.lock({ + caller: this.accounts.user0.seed, + duration: lockDuration, + payments: [ + { assetId: this.wxAssetId, amount: lockWxAmount }, + ], + chainId, + })); + + const boostingDataChanges = Object.fromEntries( + stateChanges.data.map(({ key, value }) => [key, value]), + ); + + const lockKey = keyLock(this.accounts.user0.addr, lockTxId); + lockParamsPrev = parseLockParams( + boostingDataChanges[lockKey], + ); + }); + + it('should successfully claim reward', async function () { + await waitNBlocks(1); + const { stateChanges } = await gwx.claimReward({ + dApp: this.accounts.gwx.addr, + caller: this.accounts.user0.seed, + }); + // TODO: check stateChanges + }); +}); diff --git a/test/components/boosting/contract/emission.mjs b/test/components/boosting/contract/emission.mjs index 31ac58097..1ce95c6d2 100644 --- a/test/components/boosting/contract/emission.mjs +++ b/test/components/boosting/contract/emission.mjs @@ -11,6 +11,7 @@ export const emission = { emissionDuration, wxAssetId, boostingV2StartHeight, + gwxHoldersReward, }) => { const dataTx = data({ data: [ @@ -54,6 +55,11 @@ export const emission = { type: 'integer', value: boostingV2StartHeight, }, + { + key: '%s%s__gwxHoldersReward__current', + type: 'integer', + value: gwxHoldersReward, + }, ], additionalFee: 4e5, chainId, diff --git a/test/components/boosting/contract/gwx.mjs b/test/components/boosting/contract/gwx.mjs index 43afffc7e..417a2130b 100644 --- a/test/components/boosting/contract/gwx.mjs +++ b/test/components/boosting/contract/gwx.mjs @@ -1,4 +1,4 @@ -import { data } from '@waves/waves-transactions'; +import { data, invokeScript } from '@waves/waves-transactions'; import { broadcastAndWait, chainId } from '../../../utils/api.mjs'; export const gwx = { @@ -24,4 +24,15 @@ export const gwx = { return broadcastAndWait(dataTx); }, + claimReward: async ({ + caller, + dApp, + }) => broadcastAndWait(invokeScript({ + dApp, + call: { + function: 'claimReward', + args: [], + }, + chainId, + }, caller)), }; diff --git a/test/components/boosting/mock/refferal.ride b/test/components/boosting/mock/referral.mock.ride similarity index 54% rename from test/components/boosting/mock/refferal.ride rename to test/components/boosting/mock/referral.mock.ride index a04f0fda8..42fae1a88 100644 --- a/test/components/boosting/mock/refferal.ride +++ b/test/components/boosting/mock/referral.mock.ride @@ -11,3 +11,17 @@ func createPair( ) = { ([], unit) } + +@Callable(i) +func claim(programName: String) = { + ([], 0) +} + +@Callable(i) +func updateReferralActivity( + programName: String, + referralAddress: String, + isActive: Boolean +) = { + (nil, unit) +} From 9225b8f780e08f30cfe42915fb915faa0e7b0332 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:08:51 +0500 Subject: [PATCH 76/86] gwx reward claim test math --- test/components/boosting/claimReward.mjs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/components/boosting/claimReward.mjs b/test/components/boosting/claimReward.mjs index 8f368e12e..90f0d0441 100644 --- a/test/components/boosting/claimReward.mjs +++ b/test/components/boosting/claimReward.mjs @@ -6,7 +6,7 @@ import { } from '@waves/waves-transactions'; import { - broadcastAndWait, chainId, waitNBlocks, api, + broadcastAndWait, chainId, waitForHeight, } from '../../utils/api.mjs'; import { boosting, parseLockParams, keyLock } from './contract/boosting.mjs'; import { gwx } from './contract/gwx.mjs'; @@ -18,6 +18,8 @@ describe('boosting: claimReward.mjs', /** @this {MochaSuiteModified} */() => { const lockDuration = 3; const lockWxAmount = 1e3 * 1e8; let lockTxId; + let lockHeight; + let lockParamsPrev; before(async function () { await broadcastAndWait(transfer({ @@ -48,11 +50,16 @@ describe('boosting: claimReward.mjs', /** @this {MochaSuiteModified} */() => { }); it('should successfully claim reward', async function () { - await waitNBlocks(1); - const { stateChanges } = await gwx.claimReward({ + await waitForHeight(lockHeight + 1); + const { stateChanges, height: claimHeight } = await gwx.claimReward({ dApp: this.accounts.gwx.addr, caller: this.accounts.user0.seed, }); - // TODO: check stateChanges + const transferToUser = stateChanges.transfers[0]; + const expectedAmount = Math.floor(( + this.releaseRate * this.gwxHoldersReward * (claimHeight - lockHeight) + ) / 1e8); + + expect(transferToUser.amount).to.equal(expectedAmount); }); }); From daadff89d95d7ac8a48f05753a970503a733e669 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 23 Aug 2023 18:18:12 +0500 Subject: [PATCH 77/86] claimReward 2 locks --- test/components/boosting/claimReward.mjs | 58 +++++++++++++----------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/test/components/boosting/claimReward.mjs b/test/components/boosting/claimReward.mjs index 90f0d0441..7ce77888a 100644 --- a/test/components/boosting/claimReward.mjs +++ b/test/components/boosting/claimReward.mjs @@ -2,13 +2,13 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { - transfer, + massTransfer, } from '@waves/waves-transactions'; import { broadcastAndWait, chainId, waitForHeight, } from '../../utils/api.mjs'; -import { boosting, parseLockParams, keyLock } from './contract/boosting.mjs'; +import { boosting } from './contract/boosting.mjs'; import { gwx } from './contract/gwx.mjs'; chai.use(chaiAsPromised); @@ -17,36 +17,42 @@ const { expect } = chai; describe('boosting: claimReward.mjs', /** @this {MochaSuiteModified} */() => { const lockDuration = 3; const lockWxAmount = 1e3 * 1e8; - let lockTxId; let lockHeight; - let lockParamsPrev; before(async function () { - await broadcastAndWait(transfer({ - recipient: this.accounts.user0.addr, - amount: lockWxAmount, + await broadcastAndWait(massTransfer({ + transfers: [ + { + recipient: this.accounts.user0.addr, + amount: lockWxAmount, + }, + { + recipient: this.accounts.user1.addr, + amount: lockWxAmount, + }, + ], assetId: this.wxAssetId, additionalFee: 4e5, }, this.accounts.emission.seed)); - let stateChanges; - ({ id: lockTxId, height: lockHeight, stateChanges } = await boosting.lock({ - caller: this.accounts.user0.seed, - duration: lockDuration, - payments: [ - { assetId: this.wxAssetId, amount: lockWxAmount }, - ], - chainId, - })); - - const boostingDataChanges = Object.fromEntries( - stateChanges.data.map(({ key, value }) => [key, value]), - ); - - const lockKey = keyLock(this.accounts.user0.addr, lockTxId); - lockParamsPrev = parseLockParams( - boostingDataChanges[lockKey], - ); + ([{ height: lockHeight }] = await Promise.all([ + boosting.lock({ + caller: this.accounts.user0.seed, + duration: lockDuration, + payments: [ + { assetId: this.wxAssetId, amount: lockWxAmount }, + ], + chainId, + }), + boosting.lock({ + caller: this.accounts.user1.seed, + duration: lockDuration, + payments: [ + { assetId: this.wxAssetId, amount: lockWxAmount }, + ], + chainId, + }), + ])); }); it('should successfully claim reward', async function () { @@ -58,7 +64,7 @@ describe('boosting: claimReward.mjs', /** @this {MochaSuiteModified} */() => { const transferToUser = stateChanges.transfers[0]; const expectedAmount = Math.floor(( this.releaseRate * this.gwxHoldersReward * (claimHeight - lockHeight) - ) / 1e8); + ) / (2 * 1e8)); expect(transferToUser.amount).to.equal(expectedAmount); }); From 645c8b1ae471c7d170170b97e00270107b9aaf69 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 23 Aug 2023 18:39:43 +0500 Subject: [PATCH 78/86] claim reward test refactor --- test/components/boosting/_hooks.mjs | 2 + test/components/boosting/claimReward.mjs | 17 ++++-- .../components/boosting/contract/boosting.mjs | 6 +-- test/components/boosting/contract/gwx.mjs | 54 +++++++++++++------ 4 files changed, 55 insertions(+), 24 deletions(-) diff --git a/test/components/boosting/_hooks.mjs b/test/components/boosting/_hooks.mjs index ec48adffd..3421b899a 100644 --- a/test/components/boosting/_hooks.mjs +++ b/test/components/boosting/_hooks.mjs @@ -121,6 +121,8 @@ export const mochaHooks = { boosting.maxLockDuration = this.maxLockDuration; boosting.blocksInPeriod = this.blocksInPeriod; + gwx.seed = this.accounts.gwx.seed; + await Promise.all([ managerVault.init({ caller: this.accounts.managerVault.seed, diff --git a/test/components/boosting/claimReward.mjs b/test/components/boosting/claimReward.mjs index 7ce77888a..420c77c36 100644 --- a/test/components/boosting/claimReward.mjs +++ b/test/components/boosting/claimReward.mjs @@ -9,7 +9,7 @@ import { broadcastAndWait, chainId, waitForHeight, } from '../../utils/api.mjs'; import { boosting } from './contract/boosting.mjs'; -import { gwx } from './contract/gwx.mjs'; +import { GwxReward, gwx } from './contract/gwx.mjs'; chai.use(chaiAsPromised); const { expect } = chai; @@ -62,9 +62,18 @@ describe('boosting: claimReward.mjs', /** @this {MochaSuiteModified} */() => { caller: this.accounts.user0.seed, }); const transferToUser = stateChanges.transfers[0]; - const expectedAmount = Math.floor(( - this.releaseRate * this.gwxHoldersReward * (claimHeight - lockHeight) - ) / (2 * 1e8)); + const userGwxAmount = boosting.calcGwxAmountStart({ + wxAmount: lockWxAmount, + duration: lockDuration, + }); + const totalGwxAmount = 2 * userGwxAmount; + const expectedAmount = GwxReward.calcReward({ + releaseRate: this.releaseRate, + gwxHoldersReward: this.gwxHoldersReward, + dh: claimHeight - lockHeight, + userGwxAmount, + totalGwxAmount, + }); expect(transferToUser.amount).to.equal(expectedAmount); }); diff --git a/test/components/boosting/contract/boosting.mjs b/test/components/boosting/contract/boosting.mjs index fb1dc113a..3742953b7 100644 --- a/test/components/boosting/contract/boosting.mjs +++ b/test/components/boosting/contract/boosting.mjs @@ -35,7 +35,7 @@ class Boosting { seed = ''; - address() { + get address() { return wc.address(this.seed, chainId); } @@ -107,7 +107,7 @@ class Boosting { }) { const invokeTx = invokeScript( { - dApp: this.address(), + dApp: this.address, call: { function: 'lock', args: [ @@ -128,7 +128,7 @@ class Boosting { }) { const invokeTx = invokeScript( { - dApp: this.address(), + dApp: this.address, call: { function: 'unlock', args: [ diff --git a/test/components/boosting/contract/gwx.mjs b/test/components/boosting/contract/gwx.mjs index 417a2130b..7b4c78f10 100644 --- a/test/components/boosting/contract/gwx.mjs +++ b/test/components/boosting/contract/gwx.mjs @@ -1,16 +1,32 @@ import { data, invokeScript } from '@waves/waves-transactions'; +import wc from '@waves/ts-lib-crypto'; import { broadcastAndWait, chainId } from '../../../utils/api.mjs'; -export const gwx = { - init: async ({ - caller, +export class GwxReward { + seed = ''; + + get address() { + return wc.address(this.seed, chainId); + } + + static calcReward({ + releaseRate, gwxHoldersReward, dh, userGwxAmount, totalGwxAmount, + }) { + const reward = Math.floor(( + releaseRate * gwxHoldersReward * dh * userGwxAmount + ) / (totalGwxAmount * 1e8)); + + return reward; + } + + init({ referralAddress, wxAssetId, matcherPacemakerAddress, boostingContractAddress, gwxRewardEmissionPartStartHeight, emissionContractAddress, - }) => { + }) { const dataTx = data({ data: [ { key: '%s__config', type: 'string', value: `%s%s%s__${wxAssetId}__${matcherPacemakerAddress}__${boostingContractAddress}` }, @@ -20,19 +36,23 @@ export const gwx = { ], additionalFee: 4e5, chainId, - }, caller); + }, this.seed); return broadcastAndWait(dataTx); - }, - claimReward: async ({ + } + + claimReward({ caller, - dApp, - }) => broadcastAndWait(invokeScript({ - dApp, - call: { - function: 'claimReward', - args: [], - }, - chainId, - }, caller)), -}; + }) { + return broadcastAndWait(invokeScript({ + dApp: this.address, + call: { + function: 'claimReward', + args: [], + }, + chainId, + }, caller)); + } +} + +export const gwx = new GwxReward(); From 2c7dbd5df6964728954f5d796f54a30cdf437a4e Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 23 Aug 2023 19:51:49 +0500 Subject: [PATCH 79/86] gwx claim reward for 2 parts --- test/components/boosting/claimReward.mjs | 46 ++++++++++++++++++++--- test/components/boosting/contract/gwx.mjs | 16 ++++++-- test/components/boosting/unlock.mjs | 4 +- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/test/components/boosting/claimReward.mjs b/test/components/boosting/claimReward.mjs index 420c77c36..f954ba608 100644 --- a/test/components/boosting/claimReward.mjs +++ b/test/components/boosting/claimReward.mjs @@ -30,6 +30,10 @@ describe('boosting: claimReward.mjs', /** @this {MochaSuiteModified} */() => { recipient: this.accounts.user1.addr, amount: lockWxAmount, }, + { + recipient: this.accounts.user2.addr, + amount: lockWxAmount, + }, ], assetId: this.wxAssetId, additionalFee: 4e5, @@ -55,7 +59,7 @@ describe('boosting: claimReward.mjs', /** @this {MochaSuiteModified} */() => { ])); }); - it('should successfully claim reward', async function () { + it('should successfully claim reward (simple)', async function () { await waitForHeight(lockHeight + 1); const { stateChanges, height: claimHeight } = await gwx.claimReward({ dApp: this.accounts.gwx.addr, @@ -68,11 +72,41 @@ describe('boosting: claimReward.mjs', /** @this {MochaSuiteModified} */() => { }); const totalGwxAmount = 2 * userGwxAmount; const expectedAmount = GwxReward.calcReward({ - releaseRate: this.releaseRate, - gwxHoldersReward: this.gwxHoldersReward, - dh: claimHeight - lockHeight, - userGwxAmount, - totalGwxAmount, + releaseRateList: [this.releaseRate], + gwxHoldersRewardList: [this.gwxHoldersReward], + dhList: [claimHeight - lockHeight], + userGwxAmountList: [userGwxAmount], + totalGwxAmountList: [totalGwxAmount], + }); + + expect(transferToUser.amount).to.equal(expectedAmount); + }); + + it('should successfully claim reward (2 parts)', async function () { + const { height: user2LockHeight } = await boosting.lock({ + caller: this.accounts.user2.seed, + duration: lockDuration, + payments: [ + { assetId: this.wxAssetId, amount: lockWxAmount }, + ], + chainId, + }); + await waitForHeight(user2LockHeight + 1); + const { stateChanges, height: claimHeight } = await gwx.claimReward({ + dApp: this.accounts.gwx.addr, + caller: this.accounts.user1.seed, + }); + const transferToUser = stateChanges.transfers[0]; + const userGwxAmount = boosting.calcGwxAmountStart({ + wxAmount: lockWxAmount, + duration: lockDuration, + }); + const expectedAmount = GwxReward.calcReward({ + releaseRateList: [this.releaseRate, this.releaseRate], + gwxHoldersRewardList: [this.gwxHoldersReward, this.gwxHoldersReward], + dhList: [user2LockHeight - lockHeight, claimHeight - user2LockHeight], + userGwxAmountList: [userGwxAmount, userGwxAmount], + totalGwxAmountList: [2 * userGwxAmount, 3 * userGwxAmount], }); expect(transferToUser.amount).to.equal(expectedAmount); diff --git a/test/components/boosting/contract/gwx.mjs b/test/components/boosting/contract/gwx.mjs index 7b4c78f10..01b55dd80 100644 --- a/test/components/boosting/contract/gwx.mjs +++ b/test/components/boosting/contract/gwx.mjs @@ -10,11 +10,19 @@ export class GwxReward { } static calcReward({ - releaseRate, gwxHoldersReward, dh, userGwxAmount, totalGwxAmount, + releaseRateList, gwxHoldersRewardList, dhList, userGwxAmountList, totalGwxAmountList, }) { - const reward = Math.floor(( - releaseRate * gwxHoldersReward * dh * userGwxAmount - ) / (totalGwxAmount * 1e8)); + if ( + dhList.length !== userGwxAmountList.length + || dhList.length !== totalGwxAmountList.length + ) throw new Error('invalid input data'); + let rewardRaw = 0; + for (let i = 0; i < dhList.length; i += 1) { + rewardRaw += ( + releaseRateList[i] * gwxHoldersRewardList[i] * dhList[i] * userGwxAmountList[i] + ) / (totalGwxAmountList[i] * 1e8); + } + const reward = Math.floor(rewardRaw); return reward; } diff --git a/test/components/boosting/unlock.mjs b/test/components/boosting/unlock.mjs index 231ec0408..d454b4844 100644 --- a/test/components/boosting/unlock.mjs +++ b/test/components/boosting/unlock.mjs @@ -6,7 +6,7 @@ import { } from '@waves/waves-transactions'; import { - broadcastAndWait, chainId, waitNBlocks, api, + broadcastAndWait, chainId, waitForHeight, api, } from '../../utils/api.mjs'; import { boosting, parseLockParams, keyLock } from './contract/boosting.mjs'; @@ -52,7 +52,7 @@ describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { const { height: currentHeight } = await api.blocks.fetchHeight(); let heightDiff = currentHeight - lockHeight; if (heightDiff === 0) { - await waitNBlocks(1); + await waitForHeight(currentHeight + 1); heightDiff += 1; } const { stateChanges, height: unlockHeight } = await boosting.unlock({ From 9ed69d7280ccca19d4b7b31e1387135ea8e5cc32 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 24 Aug 2023 19:47:19 +0500 Subject: [PATCH 80/86] boosting unlock all test --- test/components/boosting/unlock_all.mjs | 63 +++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 test/components/boosting/unlock_all.mjs diff --git a/test/components/boosting/unlock_all.mjs b/test/components/boosting/unlock_all.mjs new file mode 100644 index 000000000..5ff1de8a7 --- /dev/null +++ b/test/components/boosting/unlock_all.mjs @@ -0,0 +1,63 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { + transfer, +} from '@waves/waves-transactions'; + +import { + broadcastAndWait, chainId, waitForHeight, +} from '../../utils/api.mjs'; +import { boosting, parseLockParams, keyLock } from './contract/boosting.mjs'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +describe('boosting: unlock_all.mjs', /** @this {MochaSuiteModified} */() => { + const lockDuration = 2; + const lockWxAmount = 1e3 * 1e8; + let lockTxId; + let lockHeight; + + before(async function () { + await broadcastAndWait(transfer({ + recipient: this.accounts.user0.addr, + amount: lockWxAmount, + assetId: this.wxAssetId, + additionalFee: 4e5, + }, this.accounts.emission.seed)); + + ({ id: lockTxId, height: lockHeight } = await boosting.lock({ + caller: this.accounts.user0.seed, + duration: lockDuration, + payments: [ + { assetId: this.wxAssetId, amount: lockWxAmount }, + ], + chainId, + })); + }); + + it('should successfully unlock', async function () { + await waitForHeight(lockHeight + lockDuration + 1); + const { stateChanges } = await boosting.unlock({ + caller: this.accounts.user0.seed, + txId: lockTxId, + }); + const boostingDataChanges = Object.fromEntries( + stateChanges.data.map(({ key, value }) => [key, value]), + ); + + const lockKey = keyLock(this.accounts.user0.addr, lockTxId); + const lockParams = parseLockParams( + boostingDataChanges[lockKey], + ); + + const gwxAmountStart = boosting.calcGwxAmountStart({ + wxAmount: lockWxAmount, + duration: lockDuration, + }); + + expect(lockParams.wxClaimed).to.equal(lockWxAmount, 'wxClaimed'); + expect(lockParams.gwxAmount).to.equal(gwxAmountStart, 'gwxAmount'); + }); +}); From 9dcf83dad47c1cc546bd38b49698a36c373c6338 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Fri, 25 Aug 2023 18:13:29 +0500 Subject: [PATCH 81/86] update tests --- ride/boosting.ride | 4 +- test/components/boosting/_hooks.mjs | 7 ++- test/components/boosting/claimReward.mjs | 2 +- test/components/boosting/lock.mjs | 65 +++++++++++++++++++++++- test/components/boosting/unlock.mjs | 13 ++++- 5 files changed, 82 insertions(+), 9 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 40ac60f65..762e515ef 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -575,8 +575,8 @@ func lockActions(i: Invocation, duration: Int) = { let lockStart = height if ((pmtAmount < minLockAmount) && userAddress != lpStakingPoolsContract) then throwErr("amount is less then minLockAmount=" + minLockAmount.toString()) else - if (duration < minLockDuration) then throwErr("passed duration is less then minLockDuration=" + minLockDuration.toString()) else - if (duration > maxLockDuration) then throwErr("passed duration is greater then maxLockDuration=" + maxLockDuration.toString()) else + if (duration < minLockDuration) then throwErr("passed duration is less than minLockDuration=" + minLockDuration.toString()) else + if (duration > maxLockDuration) then throwErr("passed duration is greater than maxLockDuration=" + maxLockDuration.toString()) else if (duration % lockStepBlocks != 0) then throwErr("duration must be multiple of lockStepBlocks=" + lockStepBlocks.toString()) else let gWxAmountStart = fraction(pmtAmount, duration, maxLockDuration) diff --git a/test/components/boosting/_hooks.mjs b/test/components/boosting/_hooks.mjs index 3421b899a..464c6be8e 100644 --- a/test/components/boosting/_hooks.mjs +++ b/test/components/boosting/_hooks.mjs @@ -100,12 +100,11 @@ export const mochaHooks = { ]); this.minLockAmount = 500000000; - this.minDuration = 2; - this.maxDuration = 2102400; + this.minLockDuration = 2; this.maxLockDuration = 2102400; this.blocksInPeriod = 1; - this.lockStepBlocks = 1; + this.lockStepBlocks = 2; const { height } = await api.blocks.fetchHeight(); this.releaseRate = 3805175038; @@ -143,7 +142,7 @@ export const mochaHooks = { managerVaultAddress: this.accounts.managerVault.addr, lockAssetId: this.wxAssetId, minLockAmount: this.minLockAmount, - minLockDuration: this.minDuration, + minLockDuration: this.minLockDuration, maxLockDuration: this.maxLockDuration, mathContract: this.accounts.gwx.addr, blocksInPeriod: this.blocksInPeriod, diff --git a/test/components/boosting/claimReward.mjs b/test/components/boosting/claimReward.mjs index f954ba608..502cbcdbc 100644 --- a/test/components/boosting/claimReward.mjs +++ b/test/components/boosting/claimReward.mjs @@ -15,7 +15,7 @@ chai.use(chaiAsPromised); const { expect } = chai; describe('boosting: claimReward.mjs', /** @this {MochaSuiteModified} */() => { - const lockDuration = 3; + const lockDuration = 4; const lockWxAmount = 1e3 * 1e8; let lockHeight; diff --git a/test/components/boosting/lock.mjs b/test/components/boosting/lock.mjs index 7aa8c7e8e..7e422a400 100644 --- a/test/components/boosting/lock.mjs +++ b/test/components/boosting/lock.mjs @@ -12,7 +12,7 @@ chai.use(chaiAsPromised); const { expect } = chai; describe('boosting: lock.mjs', /** @this {MochaSuiteModified} */() => { - it('should successfully lock', async function () { + it('lock step blocks error', async function () { const lockDuration = 3; const lockWxAmount = 1e3 * 1e8; @@ -24,6 +24,69 @@ describe('boosting: lock.mjs', /** @this {MochaSuiteModified} */() => { additionalFee: 4e5, }, this.accounts.emission.seed)); + return expect(boosting.lock({ + caller: this.accounts.user0.seed, + duration: lockDuration, + payments: [ + { assetId: this.wxAssetId, amount: lockWxAmount }, + ], + })).to.be.rejectedWith(`duration must be multiple of lockStepBlocks=${this.lockStepBlocks}`); + }); + + it('min lock duration error', async function () { + const lockDuration = this.minLockDuration - 1; + + const lockWxAmount = 1e3 * 1e8; + + await broadcastAndWait(transfer({ + recipient: this.accounts.user0.addr, + amount: lockWxAmount, + assetId: this.wxAssetId, + additionalFee: 4e5, + }, this.accounts.emission.seed)); + + return expect(boosting.lock({ + caller: this.accounts.user0.seed, + duration: lockDuration, + payments: [ + { assetId: this.wxAssetId, amount: lockWxAmount }, + ], + })).to.be.rejectedWith(`passed duration is less than minLockDuration=${this.minLockDuration}`); + }); + + it('max lock duration error', async function () { + const lockDuration = this.maxLockDuration + 1; + + const lockWxAmount = 1e3 * 1e8; + + await broadcastAndWait(transfer({ + recipient: this.accounts.user0.addr, + amount: lockWxAmount, + assetId: this.wxAssetId, + additionalFee: 4e5, + }, this.accounts.emission.seed)); + + return expect(boosting.lock({ + caller: this.accounts.user0.seed, + duration: lockDuration, + payments: [ + { assetId: this.wxAssetId, amount: lockWxAmount }, + ], + })).to.be.rejectedWith(`passed duration is greater than maxLockDuration=${this.maxLockDuration}`); + }); + + it('should successfully lock', async function () { + const lockDuration = 4; + + const lockWxAmount = 1e3 * 1e8; + + await broadcastAndWait(transfer({ + recipient: this.accounts.user0.addr, + amount: lockWxAmount, + assetId: this.wxAssetId, + additionalFee: 4e5, + }, this.accounts.emission.seed)); + const { stateChanges, id: lockTxId } = await boosting.lock({ caller: this.accounts.user0.seed, duration: lockDuration, diff --git a/test/components/boosting/unlock.mjs b/test/components/boosting/unlock.mjs index d454b4844..30cccd759 100644 --- a/test/components/boosting/unlock.mjs +++ b/test/components/boosting/unlock.mjs @@ -14,7 +14,7 @@ chai.use(chaiAsPromised); const { expect } = chai; describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { - const lockDuration = 3; + const lockDuration = 4; const lockWxAmount = 1e3 * 1e8; let lockTxId; let lockHeight; @@ -48,6 +48,17 @@ describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { ); }); + it('nothing to unlock', async function () { + const { height: currentHeight } = await api.blocks.fetchHeight(); + + expect(currentHeight - lockHeight).to.equal(0); + + return expect(boosting.unlock({ + caller: this.accounts.user0.seed, + txId: lockTxId, + })).to.be.rejectedWith('nothing to unlock'); + }); + it('should successfully unlock', async function () { const { height: currentHeight } = await api.blocks.fetchHeight(); let heightDiff = currentHeight - lockHeight; From c06125a9ce0b371d8a0516011a114f2dc029a76f Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:35:14 +0500 Subject: [PATCH 82/86] fix getWxWithdrawable --- ride/boosting.ride | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 762e515ef..d9e6921b8 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -624,7 +624,7 @@ func getWxWithdrawable(userAddress: Address, txIdOption: ByteVector|Unit) = { { DECAY_CONSTANT * blocksInPeriod }.toBigInt() * MULT18BI, lockDuration.toBigInt() ) - let wxWithdrawable = if (height > lockEnd) then { + let wxWithdrawableTotal = if (height > lockEnd) then { userAmount } else { fraction( @@ -632,7 +632,8 @@ func getWxWithdrawable(userAddress: Address, txIdOption: ByteVector|Unit) = { MULT18BI - pow(5.toBigInt(), 1, exponent, SCALE18, SCALE18, DOWN), MULT18BI ).toInt() - } - wxClaimed + } + let wxWithdrawable = wxWithdrawableTotal - wxClaimed wxWithdrawable } From 141c8d2c15d9114259c93d8db487146c59f51d6a Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 31 Aug 2023 13:55:27 +0500 Subject: [PATCH 83/86] suspend --- ride/boosting.ride | 19 +++++++++++++++++++ ride/gwx_reward.ride | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/ride/boosting.ride b/ride/boosting.ride index d9e6921b8..7c78d315a 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -101,6 +101,10 @@ let mathContract = cfgArray[IdxCfgMathContract].addressFromString().valueOrError let blocksInPeriod = cfgArray[IdxCfgBlocksInPeriod].parseInt().valueOrErrorMessage(wrapErr("invalid blocks in period")) let lockStepBlocks = cfgArray[IdxCfgLockStepBlocks].parseInt().valueOrErrorMessage(wrapErr("invalid lock step blocks")) +let keySuspension = "%s__suspension" +let isSuspended = this.getBoolean(keySuspension).valueOrElse(false) +func throwIfSuspended() = !isSuspended || throwErr("suspended") + func getManagerVaultAddressOrThis() = { match keyManagerVaultAddress().getString() { case s:String => s.addressFromStringValue() @@ -640,6 +644,7 @@ func getWxWithdrawable(userAddress: Address, txIdOption: ByteVector|Unit) = { @Callable(i) func lockRef(duration: Int, referrerAddress: String, signature: ByteVector) = { + strict suspensionCheck = throwIfSuspended() let (lockActionsResult, gWxAmountStart) = i.lockActions(duration) let referralAddress = i.caller.toString() strict refInv = if (referrerAddress == "" || signature == base58'') then unit else { @@ -652,6 +657,7 @@ func lockRef(duration: Int, referrerAddress: String, signature: ByteVector) = { @Callable(i) func lock(duration: Int) = { + strict suspensionCheck = throwIfSuspended() let (lockActionsResult, gWxAmountStart) = i.lockActions(duration) strict updateRefActivity = mathContract.invoke("updateReferralActivity", [i.caller.toString(), gWxAmountStart], []) @@ -660,6 +666,7 @@ func lock(duration: Int) = { @Callable(i) func claimWxBoost(lpAssetIdStr: String, userAddressStr: String) = { + strict suspensionCheck = throwIfSuspended() if (stakingContract != i.caller) then throwErr("permissions denied") else let (userBoostAvailable, dataState, debug) = internalClaimWxBoost(lpAssetIdStr, userAddressStr, false) @@ -674,6 +681,7 @@ func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { @Callable(i) func unlock(txIdStr: String) = { + strict suspensionCheck = throwIfSuspended() let userAddress = i.caller let userAddressStr = userAddress.toString() let txIdOption = if (txIdStr == "") then unit else txIdStr.fromBase58String() @@ -770,6 +778,7 @@ func getGwxTotalREADONLY() = { # call this function when wxEmissionRate or boostCoeff changes @Callable(i) func onBoostEmissionUpdate() = { + strict suspensionCheck = throwIfSuspended() strict checkCaller = i.caller == emissionContract || i.mustManager() refreshBoostEmissionIntegral() } @@ -786,6 +795,7 @@ func onBoostEmissionUpdate() = { # ╰ edge = false (falling) @Callable(i) func onStakedVoteUpdate(lpAssetIdStr: String, userAddressStr: String, edge: Boolean) = { + strict suspensionCheck = throwIfSuspended() strict checkCaller = i.caller == stakingContract || i.mustManager() let actions = lpAssetIdStr.refreshVoteStakedIntegral(userAddressStr, edge) @@ -812,6 +822,15 @@ func getUserVoteStakedIntegralREADONLY(lpAssetIdStr: String, userAddressStr: Str ([], lpAssetIdStr.getUserVoteStakedIntegral(userAddressStr)) } +@Callable(i) +func suspend(v: Boolean) = { + strict checkCaller = i.mustManager() + + ([ + BooleanEntry(keySuspension, v) + ], v) +} + @Verifier(tx) func verify() = { let targetPublicKey = match managerPublicKeyOrUnit() { diff --git a/ride/gwx_reward.ride b/ride/gwx_reward.ride index d7ee61755..10ea787a9 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -135,6 +135,10 @@ func boostingContractOrFail() = { # KEYS # *********************** +let keySuspension = "%s__suspension" +let isSuspended = this.getBoolean(keySuspension).valueOrElse(false) +func throwIfSuspended() = !isSuspended || throwErr("suspended") + func keyGwxRewardEmissionStartHeight() = "%s%s__gwxRewardEmissionPart__startHeight" # boosting contract state key, increments every lock() of unique user @@ -308,6 +312,7 @@ func commonClaimReward(userAddressStr: String) = { @Callable(i) func refreshUserReward(userAddressBytes: ByteVector, userNum: Int) = { + strict suspensionCheck = throwIfSuspended() strict checkCaller = i.caller == boostingContractOrFail() || throwErr("permission denied") let (actions, reward) = _refreshUserReward(Address(userAddressBytes), userNum) @@ -324,6 +329,7 @@ func tradeRewardInternal( rewards: List[Int], currentIter: Int ) = { + strict suspensionCheck = throwIfSuspended() if (currentIter == userAddresses.size()) then ([]) else strict checks = [ @@ -357,6 +363,7 @@ func tradeRewardInternal( @Callable(i) func updateReferralActivity(userAddress: String, gWxAmountStart: Int) = { + strict suspensionCheck = throwIfSuspended() let referrer = referralsContractAddressOrFail.getString(userAddress.keyReferrer()) strict activeReferralInv = if (referrer == unit) then unit else { referralsContractAddressOrFail.invoke("updateReferralActivity", [referralProgramName, userAddress, gWxAmountStart >= referralMinGWxAmount], []) @@ -374,6 +381,7 @@ func processPendingPeriodsAndUsers() = { # called by user @Callable(i) func claimReward() = { + strict suspensionCheck = throwIfSuspended() let cfgArray = readConfigArrayOrFail() let userAddress = i.caller let userAddressStr = userAddress.toString() @@ -417,12 +425,14 @@ func claimRewardREADONLY(address: String) = { # save starting height of reward from emission 5% @Callable(i) func onEmissionForGwxStart() = { + strict suspensionCheck = throwIfSuspended() if (i.caller != factoryContract) then throw("permissions denied") else [IntegerEntry(keyGwxRewardEmissionStartHeight(), height)] } @Callable(i) func tradeReward(userAddresses: List[String], rewards: List[Int]) = { + strict suspensionCheck = throwIfSuspended() let argsComparison = userAddresses.size() == rewards.size() let maxRecipients = keyMaxRecipients().getInteger().valueOrElse(0) let payment = i.payments[0] @@ -451,6 +461,7 @@ func tradeReward(userAddresses: List[String], rewards: List[Int]) = { @Callable(i) func claimTradingReward() = { + strict suspensionCheck = throwIfSuspended() let userAddress = i.caller let userAddressString = userAddress.toString() let reward = userAddressString.getTradingReward() @@ -467,6 +478,15 @@ func claimTradingRewardREADONLY(userAddress: String) = { (nil, userAddress.getTradingReward()) } +@Callable(i) +func suspend(v: Boolean) = { + strict checkCaller = i.mustManager() + + ([ + BooleanEntry(keySuspension, v) + ], v) +} + @Verifier(tx) func verify() = { let targetPublicKey = match managerPublicKeyOrUnit() { From efc2e475cff791393383a0c0a9158ac402815646 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Thu, 31 Aug 2023 16:10:52 +0500 Subject: [PATCH 84/86] fix gwx burned math and unlock test --- ride/boosting.ride | 4 +--- test/components/boosting/contract/boosting.mjs | 4 ++-- test/components/boosting/unlock.mjs | 8 +++----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/ride/boosting.ride b/ride/boosting.ride index 7c78d315a..1fdbcbd6f 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -703,10 +703,8 @@ func unlock(txIdStr: String) = { txIdOption ) - let gWxAmountStart = fraction(userAmount, lockDuration, maxLockDuration) - let gwxBurned = min([ - fraction(t * blocksInPeriod, gWxAmountStart, maxLockDuration), + fraction(wxWithdrawable, lockDuration, maxLockDuration), gwxAmount ]) let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") diff --git a/test/components/boosting/contract/boosting.mjs b/test/components/boosting/contract/boosting.mjs index 3742953b7..0bb5884f9 100644 --- a/test/components/boosting/contract/boosting.mjs +++ b/test/components/boosting/contract/boosting.mjs @@ -54,11 +54,11 @@ class Boosting { } calcGwxAmountBurned({ - gwxAmountStart, gwxAmountPrev, passedPeriods, + wxWithdrawable, lockDuration, gwxAmountPrev, }) { const gwxBurned = Math.min( Math.floor( - (passedPeriods * this.blocksInPeriod * gwxAmountStart) / this.maxLockDuration, + (wxWithdrawable * lockDuration) / this.maxLockDuration, ), gwxAmountPrev, ); diff --git a/test/components/boosting/unlock.mjs b/test/components/boosting/unlock.mjs index 30cccd759..8755b0fa9 100644 --- a/test/components/boosting/unlock.mjs +++ b/test/components/boosting/unlock.mjs @@ -85,13 +85,11 @@ describe('boosting: unlock.mjs', /** @this {MochaSuiteModified} */() => { lockDuration, passedPeriods, }); - const gwxAmountStart = boosting.calcGwxAmountStart({ - wxAmount: lockWxAmount, - duration: lockDuration, - }); const gwxAmountPrev = lockParamsPrev.gwxAmount; const gwxBurned = boosting.calcGwxAmountBurned({ - gwxAmountStart, gwxAmountPrev, passedPeriods, + wxWithdrawable, + lockDuration, + gwxAmountPrev, }); expect(lockParams.wxClaimed).to.equal(wxWithdrawable, 'wxClaimed'); From 53fa89dcf9e45333410cd224c52e7ddb7e3f1f4b Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:59:18 +0500 Subject: [PATCH 85/86] fix migration --- migrations/wxdefi-435-release/index.mjs | 32 +++++++++++++++------- migrations/wxdefi-435-release/package.json | 4 +-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/migrations/wxdefi-435-release/index.mjs b/migrations/wxdefi-435-release/index.mjs index 7640a9cc0..a74726e76 100644 --- a/migrations/wxdefi-435-release/index.mjs +++ b/migrations/wxdefi-435-release/index.mjs @@ -2,11 +2,8 @@ import { create } from '@waves/node-api-js'; import { data } from '@waves/waves-transactions'; import fs from 'fs/promises'; import path from 'path'; -import * as dotenv from 'dotenv'; import { address } from '@waves/ts-lib-crypto'; -dotenv.config(); - const separator = '__'; const scriptedSenderFee = 4e5; @@ -22,6 +19,7 @@ const { MAX_LOCK_DURATION, BLOCKS_IN_PERIOD, LOCK_STEP_BLOCKS, + UNLOCK_START_HEIGHT, } = process.env; const api = create(NODE_URL); @@ -76,23 +74,37 @@ for (const { key, value } of lockParamsData) { const [ /* meta */, /* userNum */, - amount, - start, - duration, + lockAmount, + lockStart, + lockDuration, /* paramK */, /* paramB */, - timestamp, // last update timestamp - gwxAmount + lockTimestamp, // last update timestamp + /* gwxAmount */ ] = value.split(separator) - if (amount <= 0) continue; + if (lockAmount <= 0) continue; + const lockEnd = lockStart + lockDuration; + let gwxAmount = 0, duration = lockDuration, start = lockStart; + if (lockEnd > UNLOCK_START_HEIGHT) { + start = UNLOCK_START_HEIGHT; + duration = lockEnd - start; + gwxAmount = Math.floor((lockAmount * duration) / MAX_LOCK_DURATION); + } gwxAmountTotal += parseInt(gwxAmount); actions.push( { key: keyLockParamsRecord(userAddress, 'legacy'), type: 'string', - value: lockParamsRecord({ amount, start, duration, timestamp, gwxAmount, wxClaimed: 0 }), + value: lockParamsRecord({ + amount: lockAmount, + start, + duration, + timestamp: lockTimestamp, + gwxAmount, + wxClaimed: 0, + }), }, { key: keyUserGwxAmountTotal(userAddress), diff --git a/migrations/wxdefi-435-release/package.json b/migrations/wxdefi-435-release/package.json index cd82cf997..de323e242 100644 --- a/migrations/wxdefi-435-release/package.json +++ b/migrations/wxdefi-435-release/package.json @@ -9,7 +9,5 @@ "keywords": [], "author": "", "license": "ISC", - "dependencies": { - "dotenv": "^16.0.3" - } + "dependencies": {} } From 86ecdd630e624962f1b2455c78502a4e8e6491f1 Mon Sep 17 00:00:00 2001 From: Waves Rider <108881461+ridev6@users.noreply.github.com> Date: Tue, 5 Sep 2023 17:12:15 +0500 Subject: [PATCH 86/86] fix migration --- migrations/wxdefi-435-release/index.mjs | 53 ++++++++++++++++--------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/migrations/wxdefi-435-release/index.mjs b/migrations/wxdefi-435-release/index.mjs index a74726e76..e21d06095 100644 --- a/migrations/wxdefi-435-release/index.mjs +++ b/migrations/wxdefi-435-release/index.mjs @@ -61,9 +61,13 @@ const lockParamsData = await api.addresses.data(boostingAddress, { `%s%s__lock__[^_]+` ), }); - +let locks = { + empty: 0, + nonEmpty: 0, + unlocked: 0, +}; const actions = []; -let gwxAmountTotal = 0; +let gwxAmountTotal = 0n; for (const { key, value } of lockParamsData) { const [ /* meta */, @@ -74,25 +78,37 @@ for (const { key, value } of lockParamsData) { const [ /* meta */, /* userNum */, - lockAmount, - lockStart, - lockDuration, + lockAmountStr, + lockStartStr, + lockDurationStr, /* paramK */, /* paramB */, - lockTimestamp, // last update timestamp + lockTimestampStr, // last update timestamp /* gwxAmount */ ] = value.split(separator) - - if (lockAmount <= 0) continue; + const lockAmount = Number(lockAmountStr); + const lockStart = Number(lockStartStr); + const lockDuration = Number(lockDurationStr); + const lockTimestamp = Number(lockTimestampStr); + + if (lockAmount <= 0) { + locks.unlocked += 1; + continue; + } const lockEnd = lockStart + lockDuration; let gwxAmount = 0, duration = lockDuration, start = lockStart; - if (lockEnd > UNLOCK_START_HEIGHT) { - start = UNLOCK_START_HEIGHT; + if (lockEnd > Number(UNLOCK_START_HEIGHT)) { + start = Number(UNLOCK_START_HEIGHT); duration = lockEnd - start; - gwxAmount = Math.floor((lockAmount * duration) / MAX_LOCK_DURATION); + gwxAmount = Math.floor((lockAmount * duration) / Number(MAX_LOCK_DURATION)); } - gwxAmountTotal += parseInt(gwxAmount); + if (gwxAmount > 0) { + locks.nonEmpty += 1; + } else { + locks.empty += 1; + } + gwxAmountTotal += BigInt(gwxAmount); actions.push( { key: keyLockParamsRecord(userAddress, 'legacy'), @@ -146,7 +162,7 @@ txs.push({ { key: '%s%s__gwx__total', type: 'integer', - value: gwxAmountTotal, + value: gwxAmountTotal.toString(), }, { key: '%s__config', @@ -154,12 +170,12 @@ txs.push({ value: [ '%s%d%d%d%s%d', WX_ASSET_ID, - MIN_LOCK_AMOUNT.toString(), - MIN_LOCK_DURATION.toString(), - MAX_LOCK_DURATION.toString(), + MIN_LOCK_AMOUNT, + MIN_LOCK_DURATION, + MAX_LOCK_DURATION, gwxRewardAddress, - BLOCKS_IN_PERIOD.toString(), - LOCK_STEP_BLOCKS.toString(), + BLOCKS_IN_PERIOD, + LOCK_STEP_BLOCKS, ].join(separator), } ], @@ -190,3 +206,4 @@ await Promise.all( }) ); console.log(`Done. ${txs.length} txs created.`) +console.log({ locks });