Skip to content

Commit

Permalink
Merge branch 'wxdefi-504-low-liquidity-pools-status' into wxdefi-495-…
Browse files Browse the repository at this point in the history
…low-balance
  • Loading branch information
ridev6 committed Apr 17, 2024
2 parents 0f1ac60 + de5c847 commit f6dce94
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 58 deletions.
2 changes: 1 addition & 1 deletion ride/factory_v2.ride
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ func activateNewPool(poolAddress: String, amountAssetStr: String, priceAssetStr:
# return:
@Callable(i)
func managePool(poolAddress: String, newStatus: Int) = {
strict checkCaller = i.mustManager()
strict checkCaller = i.mustAdmin()

let poolConfig = getPoolConfig(poolAddress)

Expand Down
186 changes: 153 additions & 33 deletions ride/voting_emission.ride
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
let separator = "__"
let poolWeightMult = 100_000_000
let maxDepthDefault = 10
let finalizationStageTotal = 0
let finalizationStageShares = 1
let finalizationStageBalances = 0
let finalizationStageTotal = 1
let finalizationStageShares = 2
let resumptionFeeDefault = 5000_00000000

let keyEpochLength = ["%s", "epochLength"].makeString(separator)
let keyEpochLengthNew = ["%s%s", "epochLength__new"].makeString(separator)
func keyEpochLengthByEpoch(epoch: Int) = ["%s%d", "epochLength", epoch.toString()].makeString(separator)

let keyCurrentEpoch = ["%s", "currentEpoch"].makeString(separator)
let keyMaxDepth = ["%s", "maxDepth"].makeString(separator)
let keyResumptionFee = ["%s", "resumptionFee"].makeString(separator)
let keyVotingEmissionCandidateContract = ["%s", "votingEmissionCandidateContract"].makeString(separator)
let keyVotingEmissionRateContract = ["%s", "votingEmissionRateContract"].makeString(separator)
let keyFactoryContract = ["%s", "factoryContract"].makeString(separator)
Expand All @@ -38,6 +41,11 @@ func keyInList(pool: (String, String)) = {

["%s%s%s", "inList", amountAssetId, priceAssetId].makeString(separator)
}
func keyInsufficientBalances(pool: (String, String), epoch: Int) = {
let (amountAssetId, priceAssetId) = pool

["%s%s%s%d", "insufficientBalances", amountAssetId, priceAssetId, epoch.toString()].makeString(separator)
}
func keyUsed(address: Address, epoch: Int) =["%s%s%d", "used", address.toString(), epoch.toString()].makeString(separator)
func keyVote(pool: (String, String), address: Address, epoch: Int) = {
let (amountAssetId, priceAssetId) = pool
Expand Down Expand Up @@ -97,6 +105,17 @@ func keyFactoryCfg() = "%s__factoryConfig"
func readFactoryCfgOrFail(factory: Address) = factory.getStrOrFail(keyFactoryCfg()).split(separator)
func getGwxRewardAddressOrFail(factoryCfg: List[String]) = factoryCfg[IdxFactoryCfgGwxRewardDapp].addressFromStringValue()

# returns (poolAddress: Address, lpAssetId: ByteVector)
func getPoolInfo(amountAssetId: String, priceAssetId: String) = {
let poolInfoOption = factoryContract.invoke(
"poolInfoREADONLY",
[amountAssetId, priceAssetId],
[]
).as[(Address, ByteVector)]

poolInfoOption
}

func getLpAssetByPoolAssets(amountAssetId: String, priceAssetId: String) = {
func keyMappingsBaseAsset2internalId(baseAssetStr: String) = {
"%s%s%s__mappings__baseAsset2internalId__" + baseAssetStr
Expand Down Expand Up @@ -418,29 +437,56 @@ func setMaxDepth(newMaxDepth: Int) = {
)
}

# stages:
# 0 - расчет общего числа голосов, перенос голосов с предыдущего периода, перенос на следующий период
# 1 - расчет долей пулов, начало нового периода и изменение весов пулов - после этого этапа доступно голосование
@Callable(i)
func processPoolBalanceINTERNAL(poolStr: String) = {
strict checkCaller = i.mustThis()

# сохраняем следующий шаг, чтобы не было проблем при удалении элементов
let epoch = this.getIntOrFail(keyCurrentEpoch)
let epochPrevious = epoch - 1

# stage 0:
# pool user
# - - получить pool из head pools, есть - записать, нет - завершить
# + - получить user из head pool votes, есть - записать, нет - получить следующий пул и записать или завершить
# + + обработать, получить следующего пользователя или пул и записать или завершить
let pool = poolStr.stringToPool()
let (amountAssetId, priceAssetId) = pool
let lpAssetId = getLpAssetByPoolAssets(amountAssetId, priceAssetId)

# stage 1:
# pool
# - получить pool из head pools, нет - завершить, есть - записать
# + обработать, получить следующий пул и записать или завершить
# let wxEmission = pool.checkWxEmissionPoolLabel()

let balanceIsOkCurrent = factoryContract.invoke("checkBalance", [lpAssetId], []).exactAs[Boolean]
let balanceIsOkPrevious = !this.getBoolean(pool.keyInsufficientBalances(epochPrevious - 1)).valueOrElse(false)
# check balance, write if balances is ok
# if previous balance and current balance is not ok,
# then set status 3, remove from list, remove wx emission label
let actions = if (!balanceIsOkCurrent) then {
if (!balanceIsOkPrevious) then {
strict deleteWxEmissionInv = factoryContract.invoke("deleteWxEmissionPoolLabel", [amountAssetId, priceAssetId], [])
strict modifyWeightInv = factoryContract.invoke("modifyWeight", [lpAssetId, 0], [])

let poolAddress = getPoolInfo(
amountAssetId,
priceAssetId
).valueOrErrorMessage(wrapErr("invalid assets"))._1
let newStatus = 3
strict setPoolStatusInv = factoryContract.invoke("managePool", [poolAddress.toString(), newStatus], [])

let listActions = [
DeleteEntry(pool.keyInList())
] ++ poolsListName.deleteNodeActions(pool.poolToString())

# при переносе голосов возможно удаление пользователей из списка, если количество gwx стало равно 0
[
IntegerEntry(pool.keyPoolShare(epochPrevious), 0)
] ++ listActions
} else {
[
BooleanEntry(pool.keyInsufficientBalances(epochPrevious), true)
]
}
} else nil

(
actions,
unit
)
}

# если голоса на текущий период нет, перенести с предыдущего?
# учесть голос (totalVotes)? или totalVotes всегда актуален?
# перенести голос на следующий период (vote, used, votingResult, totalVotes)
# TODO: Сделать обычной функцией
@Callable(i)
func processVoteINTERNAL(poolStr: String, userAddressStr: String) = {
strict checkCaller = i.mustThis()
Expand All @@ -456,7 +502,6 @@ func processVoteINTERNAL(poolStr: String, userAddressStr: String) = {
strict checkTargetEpoch = epochPrevious >= 0 || "processVoteINTERNAL: invalid previous epoch".throwErr()
let pool = poolStr.stringToPool()
let (amountAssetId, priceAssetId) = pool
let wxEmission = pool.checkWxEmissionPoolLabel()
let gwxAmountAtEndTotal = this.invoke("getUserGwxAmountAtHeight", [userAddressStr, endHeight], []).exactAs[Int]
let gwxAmountAtEndTotalPrevious = this.invoke("getUserGwxAmountAtHeight", [userAddressStr, endHeightPrevious], []).exactAs[Int]
let totalVotes = epoch.keyTotalVotes().getInteger().valueOrElse(0)
Expand All @@ -475,10 +520,9 @@ func processVoteINTERNAL(poolStr: String, userAddressStr: String) = {
]

let lpAssetId = getLpAssetByPoolAssets(amountAssetId, priceAssetId)
let balanceIsOk = factoryContract.invoke("checkBalance", [lpAssetId], []).exactAs[Boolean]

let newVote = if (gwxAmountAtEndTotalPrevious > 0) then fraction(votePrevious, gwxAmountAtEndTotal, gwxAmountAtEndTotalPrevious) else 0
let actions = if (newVote > 0 && wxEmission && balanceIsOk) then {
let actions = if (newVote > 0) then {
[
IntegerEntry(pool.keyVote(userAddress, epoch), newVote),
IntegerEntry(epoch.keyTotalVotes(), totalVotes + newVote),
Expand Down Expand Up @@ -513,17 +557,12 @@ func processPoolINTERNAL(poolStr: String, force: Boolean) = {
if (r) then {
([], true)
} else {
let assetsStoreContract = this.getString(keyAssetsStoreContract).valueOrElse("invalid assets store contract").addressFromStringValue()
let wxEmission = pool.checkWxEmissionPoolLabel()
let balanceIsOk = factoryContract.invoke("checkBalance", [lpAssetId], []).exactAs[Boolean]
let amountAssetVerified = assetsStoreContract.invoke("isVerifiedREADONLY", [amountAssetId], []).exactAs[Boolean]
let priceAssetVerified = assetsStoreContract.invoke("isVerifiedREADONLY", [priceAssetId], []).exactAs[Boolean]
strict setWxEmissionInv = if (!wxEmission && balanceIsOk && amountAssetVerified && priceAssetVerified) then {
factoryContract.invoke("setWxEmissionPoolLabel", [amountAssetId, priceAssetId], [])
} else unit
let assetsStoreContract = factoryContract.getString(
keyAssetsStoreContract
).valueOrErrorMessage("invalid assets store contract").addressFromStringValue()
let totalVotes = this.getInteger(targetEpoch.keyTotalVotes()).valueOrElse(0)
let votingResult = this.getInteger(pool.keyVotingResult(targetEpoch)).valueOrElse(0)
let share = if (totalVotes == 0 || !wxEmission || !balanceIsOk) then 0 else fraction(votingResult, poolWeightMult, totalVotes)
let share = if (totalVotes == 0) then 0 else fraction(votingResult, poolWeightMult, totalVotes)
strict modifyWeightInv = factoryContract.invoke("modifyWeight", [lpAssetId, share], [])

(
Expand Down Expand Up @@ -563,7 +602,7 @@ func finalizeHelper() = {
IntegerEntry(newEpoch.keyStartHeightByEpoch(), height),
IntegerEntry(keyStartHeight, height),
IntegerEntry(keyCurrentEpoch, newEpoch),
IntegerEntry(keyFinalizationStage, finalizationStageTotal),
IntegerEntry(keyFinalizationStage, finalizationStageBalances),
IntegerEntry(epoch.keyEpochLengthByEpoch(), epochLength)
] ++ newEpochLengthActions,
true
Expand All @@ -581,6 +620,38 @@ func finalizeHelper() = {
[],
false
)
} else if (finalizationStageOrUnit == finalizationStageBalances) then {
let poolOrUnit = keyNextPool.getString()
match poolOrUnit {
case _: Unit => {
match poolsListName.keyListHead().getString() {
case _: Unit => {
([
IntegerEntry(keyFinalizationStage, finalizationStageTotal),
DeleteEntry(keyNextPool)
], true)
}
case nextPoolStr: String => {
([StringEntry(keyNextPool, nextPoolStr)], true)
}
}
}
case poolStr: String => {
strict nextPoolOrUnit = poolsListName.keyListNext(poolStr).getString()
strict r = this.invoke("processPoolBalanceINTERNAL", [poolStr], [])
match nextPoolOrUnit {
case _: Unit => {
([
IntegerEntry(keyFinalizationStage, finalizationStageTotal),
DeleteEntry(keyNextPool)
], true)
}
case nextPoolStr: String => {
([StringEntry(keyNextPool, nextPoolStr)], true)
}
}
}
}
# этап подсчета общего числа голосов
} else if (finalizationStageOrUnit == finalizationStageTotal) then {
let poolOrUnit = keyNextPool.getString()
Expand Down Expand Up @@ -789,6 +860,55 @@ func deletePool(amountAssetId: String, priceAssetId: String) = {
] ++ actions
}

# statuses are returned within 3 days
@Callable(i)
func resume(amountAssetId: String, priceAssetId: String) = {
let lpAssetId = getLpAssetByPoolAssets(amountAssetId, priceAssetId)
let balanceIsOk = factoryContract.invoke("checkBalance", [lpAssetId], []).exactAs[Boolean]

let payment = i.payments[0]

let assetsStoreContract = this.getStringValue(
keyAssetsStoreContract
).addressFromStringValue()

let kBoostingConfig = "%s__config"
let idxCfgAssetId = 1
let boostingContract = this.getStrOrFail(keyBoostingContract).addressFromStringValue()
let wxAssetId = boostingContract.getStringValue(kBoostingConfig).split(separator)[idxCfgAssetId].fromBase58String()

let amountAssetVerified = assetsStoreContract.invoke("isVerifiedREADONLY", [amountAssetId], []).exactAs[Boolean]
let priceAssetVerified = assetsStoreContract.invoke("isVerifiedREADONLY", [priceAssetId], []).exactAs[Boolean]

let resumptionFee = this.getInteger(
keyResumptionFee
).valueOrElse(resumptionFeeDefault)

strict checks = [
balanceIsOk || throwErr("insufficient balances"),
i.payments.size() == 1 || throwErr("1 payment is required"),
payment.assetId == wxAssetId || throwErr("invalid payment asset id"),
payment.amount == resumptionFee || throwErr("invalid payment amount"),
amountAssetVerified && priceAssetVerified || throwErr("both assets should be verified")
]

# strict setWxEmissionInv = factoryContract.invoke("setWxEmissionPoolLabel", [amountAssetId, priceAssetId], [])

# let poolAddress = getPoolInfo(
# amountAssetId,
# priceAssetId
# ).valueOrErrorMessage(wrapErr("invalid assets"))._1
# let newStatus = 1
# strict setPoolStatusInv = factoryContract.invoke("managePool", [poolAddress.toString(), newStatus], [])

let pool = (amountAssetId, priceAssetId)
let inListActions = [
BooleanEntry(pool.keyInList(), true)
] ++ poolsListName.insertNodeActions(pool.poolToString())

(inListActions, unit)
}

@Verifier(tx)
func verify() = {
let targetPublicKey = match managerPublicKeyOrUnit() {
Expand Down
34 changes: 34 additions & 0 deletions test/common_mock/factory_v2.mock.ride
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,26 @@
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}

let wavesString = "WAVES"

# pool labels
let poolLabelWxEmission = "WX_EMISSION"
let validPoolLabels = [poolLabelWxEmission]

func keyWxEmission(amountAssetId: String, priceAssetId: String)
= "%s%s%s__wxEmission__" + amountAssetId + "__" + priceAssetId

func keyMappingsBaseAsset2internalId(baseAssetStr: String)
= "%s%s%s__mappings__baseAsset2internalId__" + baseAssetStr
func keyMappingPoolAssetsToPoolContractAddress(internalAmountAssetIdStr: Int, internalPriceAssetIdStr: Int)
= "%d%d%s%s__" + internalAmountAssetIdStr.toString() + "__" + internalPriceAssetIdStr.toString() + "__mappings__poolAssets2PoolContract"
func keyMappingPoolContractToLPAsset(poolContractAddress: String)
= "%s%s%s__" + poolContractAddress + "__mappings__poolContract2LpAsset"

func parseAssetId(input: String) = {
if (input == wavesString) then unit else input.fromBase58String()
}

@Callable(i)
func activateNewPool(poolAddress: String, amountAssetStr: String, priceAssetStr: String, lpAssetName: String, lpAssetDescr: String, poolWeight: Int, poolType: String, logo: String) = {
let lpAssetIssueAction = Issue(lpAssetName, lpAssetDescr, 1, 8, true)
Expand All @@ -17,6 +30,27 @@ func activateNewPool(poolAddress: String, amountAssetStr: String, priceAssetStr:
([lpAssetIssueAction], lpAssetIdStr)
}

@Callable(i)
func poolInfoREADONLY(amountAssetIdStr: String, priceAssetIdStr: String) = {
let amountAssetInternalIdOption = this.getInteger(amountAssetIdStr.keyMappingsBaseAsset2internalId())
let priceAssetInternalIdOption = this.getInteger(priceAssetIdStr.keyMappingsBaseAsset2internalId())
let poolContractAddressOption = this.getString(keyMappingPoolAssetsToPoolContractAddress(amountAssetInternalIdOption.value(), priceAssetInternalIdOption.value()))
let lpAssetIdOption = this.getString(poolContractAddressOption.value().keyMappingPoolContractToLPAsset())
let poolExists = amountAssetInternalIdOption.isDefined() && priceAssetInternalIdOption.isDefined() && poolContractAddressOption.isDefined()

let poolInfo = if (poolExists) then {
(
poolContractAddressOption.value().addressFromStringValue(),
lpAssetIdOption.value().parseAssetId()
)
} else unit

([], poolInfo)
}

@Callable(i)
func managePool(poolAddress: String, newStatus: Int) = (nil, unit)

@Callable(i)
func checkWxEmissionPoolLabel(amountAssetId: String, priceAssetId: String) = {
let haveLabel = match keyWxEmission(amountAssetId, priceAssetId).getBoolean() {
Expand Down
5 changes: 4 additions & 1 deletion test/components/voting_emission/_hooks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ const boostingMockPath = format({ dir: testPath, base: 'boosting.mock.ride' });
const factoryMockPath = format({ dir: testPath, base: 'factory_v2.mock.ride' });
const stakingMockPath = format({ dir: testPath, base: 'staking.mock.ride' });
const gwxRewardMockPath = format({ dir: testPath, base: 'gwx_reward.mock.ride' });
const assetsStoreMockPath = format({ dir: testPath, base: 'assets_store.mock.ride' });
const votingEmissionRateMockPath = format({ dir: mocksDir, base: 'voting_emission_rate.ride' });

export const mochaHooks = {
async beforeAll() {
const nonce = random(nonceLength, 'Buffer').toString('hex');
this.votingDuration = 3;
// setup accounts
const contractNames = ['votingEmission', 'votingEmissionCandidate', 'boosting', 'staking', 'factory', 'assetsStore', 'gwxReward', 'votingEmissionRate'];
const contractNames = ['votingEmission', 'votingEmissionCandidate', 'boosting', 'staking', 'factory', 'assetsStore', 'gwxReward', 'votingEmissionRate', 'pool1', 'pool2'];
const userNames = Array.from({ length: 3 }, (_, k) => `user${k}`);
const names = [...contractNames, ...userNames, 'pacemaker'];
this.accounts = Object.fromEntries(names.map((item) => {
Expand All @@ -44,6 +45,7 @@ export const mochaHooks = {
await broadcastAndWait(data({
data: [
{ key: '%s__factoryConfig', type: 'string', value: ['%s', '1', '2', '3', '4', '5', '6', '7', '8', '9', this.accounts.gwxReward.addr, '11'].join('__') },
{ key: '%s__assetsStoreContract', type: 'string', value: this.accounts.assetsStore.addr },
],
chainId,
}, this.accounts.factory.seed));
Expand All @@ -54,5 +56,6 @@ export const mochaHooks = {
await setScriptFromFile(stakingMockPath, this.accounts.staking.seed);
await setScriptFromFile(gwxRewardMockPath, this.accounts.gwxReward.seed);
await setScriptFromFile(votingEmissionRateMockPath, this.accounts.votingEmissionRate.seed);
await setScriptFromFile(assetsStoreMockPath, this.accounts.assetsStore.seed);
},
};
21 changes: 21 additions & 0 deletions test/components/voting_emission/callables.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,27 @@ export const votingEmission = {
);
return broadcastAndWait(invokeTx);
},

setMaxDepth: async ({
dApp, caller, value,
}) => {
const invokeTx = invokeScript(
{
dApp,
call: {
function: 'setMaxDepth',
args: [
{ type: 'integer', value },
],
},
payment: [],
additionalFee: 4e5,
chainId,
},
caller,
);
return broadcastAndWait(invokeTx);
},
};

export const boostingMock = {
Expand Down
Loading

0 comments on commit f6dce94

Please sign in to comment.