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..e21d06095 --- /dev/null +++ b/migrations/wxdefi-435-release/index.mjs @@ -0,0 +1,209 @@ +import { create } from '@waves/node-api-js'; +import { data } from '@waves/waves-transactions'; +import fs from 'fs/promises'; +import path from 'path'; +import { address } from '@waves/ts-lib-crypto'; + +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, + BLOCKS_IN_PERIOD, + LOCK_STEP_BLOCKS, + UNLOCK_START_HEIGHT, +} = 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 keyLockParamsRecord = (userAddress, txId) => + `%s%s%s__lock__${userAddress}__${txId}`; + +const keyUserGwxAmountTotal = (userAddress) => + `%s%s__gwxAmountTotal__${userAddress}`; + +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__[^_]+` + ), +}); +let locks = { + empty: 0, + nonEmpty: 0, + unlocked: 0, +}; +const actions = []; +let gwxAmountTotal = 0n; +for (const { key, value } of lockParamsData) { + const [ + /* meta */, + /* 'lock' */, + userAddress + ] = key.split(separator); + + const [ + /* meta */, + /* userNum */, + lockAmountStr, + lockStartStr, + lockDurationStr, + /* paramK */, + /* paramB */, + lockTimestampStr, // last update timestamp + /* gwxAmount */ + ] = value.split(separator) + 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 > Number(UNLOCK_START_HEIGHT)) { + start = Number(UNLOCK_START_HEIGHT); + duration = lockEnd - start; + gwxAmount = Math.floor((lockAmount * duration) / Number(MAX_LOCK_DURATION)); + } + + if (gwxAmount > 0) { + locks.nonEmpty += 1; + } else { + locks.empty += 1; + } + gwxAmountTotal += BigInt(gwxAmount); + actions.push( + { + key: keyLockParamsRecord(userAddress, 'legacy'), + type: 'string', + value: lockParamsRecord({ + amount: lockAmount, + start, + duration, + timestamp: lockTimestamp, + gwxAmount, + wxClaimed: 0, + }), + }, + { + key: keyUserGwxAmountTotal(userAddress), + type: 'integer', + value: parseInt(gwxAmount), + }, + ); +} +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.toString(), + }, + { + key: '%s__config', + type: 'string', + value: [ + '%s%d%d%d%s%d', + WX_ASSET_ID, + MIN_LOCK_AMOUNT, + MIN_LOCK_DURATION, + MAX_LOCK_DURATION, + gwxRewardAddress, + BLOCKS_IN_PERIOD, + LOCK_STEP_BLOCKS, + ].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) + ); + }) +); +console.log(`Done. ${txs.length} txs created.`) +console.log({ locks }); diff --git a/migrations/wxdefi-435-release/package.json b/migrations/wxdefi-435-release/package.json new file mode 100644 index 000000000..de323e242 --- /dev/null +++ b/migrations/wxdefi-435-release/package.json @@ -0,0 +1,13 @@ +{ + "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": {} +} diff --git a/ride/boosting.ride b/ride/boosting.ride index ad440637f..1fdbcbd6f 100644 --- a/ride/boosting.ride +++ b/ride/boosting.ride @@ -6,45 +6,36 @@ # * "%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%d%d______________") # * "%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() +let DECAY_CONSTANT = 8 -func wrapErr(msg: String) = ["boosting.ride:", msg].makeString(" ") +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 +43,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" @@ -92,42 +81,29 @@ 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 +let IdxCfgAssetId = 1 +let IdxCfgMinLockAmount = 2 +let IdxCfgMinLockDuration = 3 +let IdxCfgMaxLockDuration = 4 +let IdxCfgMathContract = 5 +let IdxCfgBlocksInPeriod = 6 +let IdxCfgLockStepBlocks = 7 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 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")) +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() { @@ -153,74 +129,62 @@ 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|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 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", # 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 +229,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 +240,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 +293,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 +306,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|Unit, + 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 +482,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 +507,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 +521,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 @@ -608,85 +558,93 @@ 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 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) && userAddress != lpStakingPoolsContract) then throwErr("amount is less then minLockAmount=" + minLockAmount.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 - 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 gWxAmountStart = fraction(pmtAmount, duration, maxLockDuration) - let coeffX8 = fraction(duration, MULT8, maxLockDuration) - let gWxAmountStart = fraction(pmtAmount, coeffX8, MULT8) + let gwxAmountTotal = getGwxAmountTotal() - 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 userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNum) + let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 - let totalCachedGwxRaw = getTotalCachedGwx(false) + let userGwxAmountTotal = getUserGwxAmountTotal(userAddress) - let userBoostEmissionLastIntegralKEY = keyUserBoostEmissionLastINTEGRAL(userNumStr) - let boostEmissionIntegral = refreshBoostEmissionIntegral()._2 + strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes, userNum], []) 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) } -@Callable(i) -func constructor(factoryAddressStr: String, lockAssetIdStr: String, minLockAmount: Int, minDuration: Int, maxDuration: Int, mathContract: String) = { - strict checkCaller = i.mustManager() +func getWxWithdrawable(userAddress: Address, txIdOption: ByteVector|Unit) = { + let userRecordArray = readLockParamsRecordOrFail( + userAddress, + txIdOption + ) - [IntegerEntry(keyNextUserNum(), 0), - StringEntry( - keyConfig(), - formatConfig(lockAssetIdStr, minLockAmount, minDuration, maxDuration, mathContract)), - StringEntry(keyFactoryAddress(), factoryAddressStr) - ] - ++ StatsEntry(0, 0, 0, 0) + 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 t = (height - lockStart) / blocksInPeriod + + let exponent = fraction( + t.toBigInt(), + { DECAY_CONSTANT * blocksInPeriod }.toBigInt() * MULT18BI, + lockDuration.toBigInt() + ) + let wxWithdrawableTotal = if (height > lockEnd) then { + userAmount + } else { + fraction( + userAmount.toBigInt(), + MULT18BI - pow(5.toBigInt(), 1, exponent, SCALE18, SCALE18, DOWN), + MULT18BI + ).toInt() + } + let wxWithdrawable = wxWithdrawableTotal - wxClaimed + + wxWithdrawable } @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 { @@ -699,68 +657,16 @@ 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], []) (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) = { + strict suspensionCheck = throwIfSuspended() if (stakingContract != i.caller) then throwErr("permissions denied") else let (userBoostAvailable, dataState, debug) = internalClaimWxBoost(lpAssetIdStr, userAddressStr, false) @@ -774,71 +680,103 @@ func claimWxBoostREADONLY(lpAssetIdStr: String, userAddressStr: String) = { } @Callable(i) -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 +func unlock(txIdStr: String) = { + strict suspensionCheck = throwIfSuspended() + let userAddress = i.caller + let userAddressStr = userAddress.toString() + let txIdOption = if (txIdStr == "") then unit else txIdStr.fromBase58String() + let userRecordArray = readLockParamsRecordOrFail( + userAddress, + txIdOption + ) - let cfgArray = readConfigArrayOrFail() - let assetId = cfgArray[IdxCfgAssetId].fromBase58String() + let userAmount = userRecordArray[IdxLockAmount].parseIntValue() + let lockStart = userRecordArray[IdxLockStart].parseIntValue() + let lockDuration = userRecordArray[IdxLockDuration].parseIntValue() + let wxClaimed = userRecordArray[IdxLockWxClaimed].parseIntValue() + let gwxAmount = userRecordArray[IdxLockGwxAmount].parseIntValue() - if (lockEnd >= height) then throwErr("wait " + lockEnd.toString() + " to unlock") else - if (userAmount <= 0) then throwErr("nothing to unlock") else + let t = (height - lockStart) / blocksInPeriod - let period = mathContract.getInteger(keyNextPeriod()).valueOrElse(0) + let wxWithdrawable = getWxWithdrawable( + userAddress, + txIdOption + ) - 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", userAddress, userAmount, lockStart, lockDuration, 0, 0, i) - :+ ScriptTransfer(userAddress.addressFromStringValue(), userAmount, assetId) + let gwxBurned = min([ + fraction(wxWithdrawable, lockDuration, maxLockDuration), + gwxAmount + ]) + let gwxRemaining = ensurePositive(gwxAmount - gwxBurned, "gwxRemaining") + let lockedGwxAmount = getLockedGwxAmount(userAddress) + + if (wxWithdrawable <= 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 + let userNum = getString(keyUser2NumMapping(userAddressStr)) + .valueOrErrorMessage(wrapErr("invalid user number")).parseIntValue() + strict gwxRewardInv = gwxRewardContract.invoke("refreshUserReward", [userAddress.bytes, userNum], []) + + 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) + ++ [ + IntegerEntry(keyGwxTotal(), ensurePositive(gwxAmountTotal - gwxBurned, "gwxTotal")), + IntegerEntry(keyUserGwxAmountTotal(userAddress), userGwxAmountTotalNew) + ] } @Callable(i) -func gwxUserInfoREADONLY(userAddress: String) = { - let gwxAmount = calcCurrentGwxAmount(userAddress) +func gwxUserInfoREADONLY(userAddressStr: String) = { + let userAddress = userAddressStr.addressFromString() + .valueOrErrorMessage(wrapErr("invalid user address")) - ([], [gwxAmount]) + let gwxAmount = getUserGwxAmountTotal(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]) + ([], ("lock", 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 @Callable(i) func onBoostEmissionUpdate() = { + strict suspensionCheck = throwIfSuspended() strict checkCaller = i.caller == emissionContract || i.mustManager() refreshBoostEmissionIntegral() } @@ -855,6 +793,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) @@ -881,6 +820,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 179dd9da3..10ea787a9 100644 --- a/ride/gwx_reward.ride +++ b/ride/gwx_reward.ride @@ -9,27 +9,32 @@ # * "%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 MULT8BI = MULT8.toBigInt() 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 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 @@ -130,10 +135,15 @@ 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 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" @@ -143,68 +153,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,64 +207,119 @@ 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 getTradingReward(userAddress: String) = { + this.getInteger(userAddress.keyTradingReward()).valueOrElse(0) +} -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 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 + 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 = getGwxAmountTotalOption().valueOrElse(0) + let dh = { height - rewardPerGwxIntegralLastHeight }.toBigInt() + let gwxAmountTotalBI = gwxAmountTotal.toBigInt() + let rewardPerGwxIntegral = rewardPerGwxIntegralPrevious + if (gwxAmountTotalBI == zeroBigInt) then zeroBigInt else fraction( + dh, + emissionRate.toBigInt() * gwxHoldersRewardCurrent.toBigInt() * MULT18BI, + gwxAmountTotalBI * MULT8BI + ) + + ( + [ + 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, userNum: Int) = { + let (rewardPerGwxIntegralActions, rewardPerGwxIntegral) = _refreshRewardPerGwxIntegral() + + let rewardPerGwxIntegralUserLast = { + match this.getString(keyRewardPerGwxIntegralUserLast(userAddress)) { + case s: String => s.parseBigInt().valueOrErrorMessage(wrapErr("invalid user last integral")) + case _: Unit => rewardPerGwxIntegral + } } + + let userIdxOption = boostingContractOrFail().getString(keyUser2NumMapping(userAddress)) + let userUnclaimed = keyUserUnclaimed(userNum).getInteger().valueOrElse(0) + + # userReward = userGwxAmount * (rewardPerGwxIntegral - rewardPerGwxIntegralUserLast) + userRewardPrevious - userRewardClaimed + let userGwxAmount = getUserGwxAmountTotalOption(userAddress).valueOrElse(0) + let userReward = fraction(userGwxAmount.toBigInt(), rewardPerGwxIntegral - rewardPerGwxIntegralUserLast, MULT18BI).toInt() + userUnclaimed + + ( + [ + StringEntry( + keyRewardPerGwxIntegralUserLast(userAddress), + rewardPerGwxIntegral.toString() + ) + ] ++ rewardPerGwxIntegralActions, + userReward + ) } -func getTradingReward(userAddress: String) = { - this.getInteger(userAddress.keyTradingReward()).valueOrElse(0) +func commonClaimReward(userAddressStr: String) = { + let userAddress = addressFromString(userAddressStr).valueOrErrorMessage(wrapErr("invalid user address")) + let cfgArray = readConfigArrayOrFail() + let userNum = getUserIndexByAddress(cfgArray[IdxCfgBoostingContract], userAddressStr) # will throw if no such user + let (actions, reward) = _refreshUserReward(userAddress, userNum) + + ( + reward, + [ + IntegerEntry(userNum.keyUserUnclaimed(), 0) + ] ++ actions + ) +} + +@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) + + ([ + IntegerEntry(userNum.keyUserUnclaimed(), reward) + ] ++ actions, reward) } @Callable(i) @@ -323,6 +329,7 @@ func tradeRewardInternal( rewards: List[Int], currentIter: Int ) = { + strict suspensionCheck = throwIfSuspended() if (currentIter == userAddresses.size()) then ([]) else strict checks = [ @@ -356,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], []) @@ -364,144 +372,44 @@ 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 # called by user @Callable(i) func claimReward() = { + strict suspensionCheck = throwIfSuspended() 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 emit = emissionContract.invoke("emit", [amount], []) + + 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, wxAssetId), + HistoryEntry("claim", userAddressStr, totalAmount, i) + ] ++ actions, + totalAmount + ) } # returns total claimable reward by user address @@ -514,105 +422,17 @@ 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() = { + strict suspensionCheck = throwIfSuspended() if (i.caller != factoryContract) then throw("permissions denied") else [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]) = { + strict suspensionCheck = throwIfSuspended() let argsComparison = userAddresses.size() == rewards.size() let maxRecipients = keyMaxRecipients().getInteger().valueOrElse(0) let payment = i.payments[0] @@ -641,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() @@ -657,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() { diff --git a/ride/proposal.ride b/ride/proposal.ride index 7c5f0f1f6..165c840a5 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() @@ -29,6 +33,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 +58,9 @@ func formatProposalInfo( SEP) } +let idxUserVoteOnProposalVote = 1 +let idxUserVoteOnProposalGwxNumber = 2 + func formatUserVoteOnProposal(vote: String, gwxNumber: String) = { makeString([ "%d%d", # 0 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 ba1d51181..ecd337ca1 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)) = { @@ -505,28 +498,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 + ) } # Может вызвать любой @@ -663,7 +651,6 @@ func finalizeHelper() = { IntegerEntry(keyStartHeightUi, startHeight) ] } - strict gwxRewardDepositInv = gwxRewardDeposit() (actions, true) } case nextPoolStr: String => { @@ -694,7 +681,6 @@ func finalizeHelper() = { DeleteEntry(keyNextPool) ] } - strict gwxRewardDepositInv = gwxRewardDeposit() (actions, true) } case nextPoolStr: String => { @@ -783,6 +769,19 @@ func deletePool(amountAssetId: String, priceAssetId: String) = { ] ++ actions } +@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() { 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() { diff --git a/test/components/boosting/_hooks.mjs b/test/components/boosting/_hooks.mjs index a8092ab36..464c6be8e 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'; @@ -17,12 +19,12 @@ 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 { CHAIN_ID: chainId, BASE_SEED: baseSeed } = process.env; 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' }); @@ -33,7 +35,7 @@ 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 = { async beforeAll() { @@ -49,11 +51,11 @@ export const mochaHooks = { 'lp', 'factory', 'votingEmission', + 'votingEmisisonRate', 'referral', 'votingEmissionCandidate', 'manager', 'managerVault', - 'referral', 'referrer', 'lpStakingPools', ]; @@ -83,47 +85,26 @@ 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(votingEmissionRatePath, this.accounts.votingEmisisonRate.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.minLockDuration = 2; this.maxLockDuration = 2102400; - 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, - }); + this.blocksInPeriod = 1; + this.lockStepBlocks = 2; const { height } = await api.blocks.fetchHeight(); this.releaseRate = 3805175038; @@ -131,70 +112,117 @@ export const mochaHooks = { this.emissionStartBlock = 1806750; 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, - }); - - 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.gwxHoldersReward = 0.2 * 1e8; 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, - }); + boosting.seed = this.accounts.boosting.seed; + boosting.maxLockDuration = this.maxLockDuration; + boosting.blocksInPeriod = this.blocksInPeriod; + + gwx.seed = this.accounts.gwx.seed; + + 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({ + 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.minLockDuration, + 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, + gwxHoldersReward: this.gwxHoldersReward, + }), + + 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, + votingEmissionRateAddress: this.accounts.votingEmisisonRate.addr, + }), + + 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, + }), + + votingEmissionRate.init({ + dApp: this.accounts.votingEmisisonRate.addr, + caller: this.accounts.votingEmisisonRate.seed, + startHeight: height, + }), + ]); ({ lpAssetId: this.lpAssetId } = await factory.createPool({ amountAssetId: this.wxAssetId, @@ -211,6 +239,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/claimReward.mjs b/test/components/boosting/claimReward.mjs new file mode 100644 index 000000000..502cbcdbc --- /dev/null +++ b/test/components/boosting/claimReward.mjs @@ -0,0 +1,114 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { + massTransfer, +} from '@waves/waves-transactions'; + +import { + broadcastAndWait, chainId, waitForHeight, +} from '../../utils/api.mjs'; +import { boosting } from './contract/boosting.mjs'; +import { GwxReward, gwx } from './contract/gwx.mjs'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +describe('boosting: claimReward.mjs', /** @this {MochaSuiteModified} */() => { + const lockDuration = 4; + const lockWxAmount = 1e3 * 1e8; + let lockHeight; + + before(async function () { + await broadcastAndWait(massTransfer({ + transfers: [ + { + recipient: this.accounts.user0.addr, + amount: lockWxAmount, + }, + { + recipient: this.accounts.user1.addr, + amount: lockWxAmount, + }, + { + recipient: this.accounts.user2.addr, + amount: lockWxAmount, + }, + ], + assetId: this.wxAssetId, + additionalFee: 4e5, + }, this.accounts.emission.seed)); + + ([{ 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 (simple)', async function () { + await waitForHeight(lockHeight + 1); + const { stateChanges, height: claimHeight } = await gwx.claimReward({ + dApp: this.accounts.gwx.addr, + caller: this.accounts.user0.seed, + }); + const transferToUser = stateChanges.transfers[0]; + const userGwxAmount = boosting.calcGwxAmountStart({ + wxAmount: lockWxAmount, + duration: lockDuration, + }); + const totalGwxAmount = 2 * userGwxAmount; + const expectedAmount = GwxReward.calcReward({ + 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/claimWxBoostRejectIfUnsupportedLpAsset.mjs b/test/components/boosting/claimWxBoostRejectIfUnsupportedLpAsset.mjs index d8d346221..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: this.maxLockDuration, + duration, 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/contract/boosting.mjs b/test/components/boosting/contract/boosting.mjs index 70625ab5d..0bb5884f9 100644 --- a/test/components/boosting/contract/boosting.mjs +++ b/test/components/boosting/contract/boosting.mjs @@ -1,9 +1,72 @@ import { data, invokeScript } from '@waves/waves-transactions'; -import { broadcastAndWait, chainId } from '../../../utils/api.mjs'; +import wc from '@waves/ts-lib-crypto'; +import { broadcastAndWait, chainId, separator } from '../../../utils/api.mjs'; -export const boosting = { - init: async ({ - caller, +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), + }; +}; + +const decayConstant = 8; +class Boosting { + maxLockDuration = 0; + + blocksInPeriod = 0; + + seed = ''; + + get 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({ + wxWithdrawable, lockDuration, gwxAmountPrev, + }) { + const gwxBurned = Math.min( + Math.floor( + (wxWithdrawable * lockDuration) / this.maxLockDuration, + ), + gwxAmountPrev, + ); + + return gwxBurned; + } + + async init({ factoryAddress, referralsAddress, votingEmissionAddress, @@ -15,7 +78,9 @@ export const boosting = { maxLockDuration, mathContract, nextUserNum = 0, - }) => { + blocksInPeriod, + lockStepBlocks, + }) { const dataTx = data({ data: [ { key: '%s%s__config__factoryAddress', type: 'string', value: factoryAddress }, @@ -25,24 +90,24 @@ 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 }, ], 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: [ @@ -56,26 +121,28 @@ export const boosting = { caller, ); return broadcastAndWait(invokeTx); - }, + } - increaseLock: async ({ - dApp, caller, deltaDuration, payments = [], - }) => { + async unlock({ + caller, txId, + }) { const invokeTx = invokeScript( { - dApp, + dApp: this.address, call: { - function: 'increaseLock', + function: 'unlock', args: [ - { type: 'integer', value: deltaDuration }, + { type: 'string', value: txId }, ], }, - payment: payments, + payment: [], additionalFee: 4e5, chainId, }, caller, ); return broadcastAndWait(invokeTx); - }, -}; + } +} + +export const boosting = new Boosting(); 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 c99807302..01b55dd80 100644 --- a/test/components/boosting/contract/gwx.mjs +++ b/test/components/boosting/contract/gwx.mjs @@ -1,19 +1,66 @@ -import { data } from '@waves/waves-transactions'; +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({ + releaseRateList, gwxHoldersRewardList, dhList, userGwxAmountList, totalGwxAmountList, + }) { + 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; + } + + init({ 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, - }, caller); + }, this.seed); return broadcastAndWait(dataTx); - }, -}; + } + + claimReward({ + caller, + }) { + return broadcastAndWait(invokeScript({ + dApp: this.address, + call: { + function: 'claimReward', + args: [], + }, + chainId, + }, caller)); + } +} + +export const gwx = new GwxReward(); 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/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/gwxUserInfoREADONLY.mjs b/test/components/boosting/gwxUserInfoREADONLY.mjs index 1d283850b..deddc3439 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,25 @@ describe('boosting: gwxUserInfoREADONLY.mjs', /** @this {MochaSuiteModified} */( }, 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: this.maxLockDuration, + duration, 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 expectedGwxAmount = Math.floor(( + wxAmount * duration + ) / this.maxLockDuration); // TODO: check all checkData expect(checkData[0]).to.eql({ 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/lock.mjs b/test/components/boosting/lock.mjs new file mode 100644 index 000000000..7e422a400 --- /dev/null +++ b/test/components/boosting/lock.mjs @@ -0,0 +1,117 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { transfer } from '@waves/waves-transactions'; + +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('lock step blocks error', 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)); + + 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, + 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 = boosting.calcGwxAmountStart({ + wxAmount: lockWxAmount, + duration: lockDuration, + }); + 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/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/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}`, - ); - }, - ); -}); 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) +} diff --git a/test/components/boosting/unlock.mjs b/test/components/boosting/unlock.mjs new file mode 100644 index 000000000..8755b0fa9 --- /dev/null +++ b/test/components/boosting/unlock.mjs @@ -0,0 +1,98 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { + transfer, +} from '@waves/waves-transactions'; + +import { + broadcastAndWait, chainId, waitForHeight, 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 = 4; + const lockWxAmount = 1e3 * 1e8; + let lockTxId; + let lockHeight; + let lockParamsPrev; + + 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('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; + if (heightDiff === 0) { + await waitForHeight(currentHeight + 1); + heightDiff += 1; + } + const { stateChanges, height: unlockHeight } = 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 passedPeriods = Math.floor((unlockHeight - lockHeight) / this.blocksInPeriod); + const wxWithdrawable = boosting.calcWxWithdrawable({ + lockWxAmount, + lockDuration, + passedPeriods, + }); + const gwxAmountPrev = lockParamsPrev.gwxAmount; + const gwxBurned = boosting.calcGwxAmountBurned({ + wxWithdrawable, + lockDuration, + gwxAmountPrev, + }); + + expect(lockParams.wxClaimed).to.equal(wxWithdrawable, 'wxClaimed'); + expect(lockParams.gwxAmount).to.equal(gwxAmountPrev - gwxBurned, 'gwxAmount'); + }); +}); 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, - ); - }, - ); -}); 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'); + }); +}); 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 + }, + ); +}); 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/active_voting_list.spec.mjs b/test/components/proposal/active_voting_list.spec.mjs new file mode 100644 index 000000000..b179e7bea --- /dev/null +++ b/test/components/proposal/active_voting_list.spec.mjs @@ -0,0 +1,22 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { proposal } from './contract/proposal.mjs'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +describe(`${process.pid}: proposal: start new vote`, () => { + it('new vote should be created', async function () { + const duration = 60; + const quorumNumber = 1e8; + + return expect(proposal.startNewVote({ + dApp: this.accounts.proposal.address, + caller: this.accounts.proposal.seed, + name: 'test1', + description: 'test1', + duration, + quorumNumber, + })).to.be.fulfilled; + }); +}); 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/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 ed7a94090..05da4e7d0 100644 --- a/test/package.json +++ b/test/package.json @@ -21,12 +21,13 @@ "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", "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", "test-l2mp-staking": "mocha -r dotenv/config --parallel --require components/l2mp_staking/_hooks.mjs components/l2mp_staking", "test-l2mp-swap": "mocha -r dotenv/config --parallel --require components/l2mp_swap/_hooks.mjs components/l2mp_swap", "ci-test": "node_modules/.bin/concurrently --max-processes 8 --timings npm:test-*", 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