From d2e7f671d070e9ee31ac8d28462637dfb619a57a Mon Sep 17 00:00:00 2001 From: Bushstar Date: Mon, 4 Mar 2024 15:50:11 +0000 Subject: [PATCH 01/13] FutureSwap limitation --- src/dfi/consensus/smartcontracts.cpp | 41 +++ src/dfi/errors.h | 2 + src/dfi/govvariables/attributes.cpp | 55 +++- src/dfi/govvariables/attributes.h | 2 + src/dfi/poolpairs.cpp | 40 +++ src/dfi/poolpairs.h | 31 ++ src/dfi/rpc_poolpair.cpp | 48 ++- src/dfi/validation.cpp | 160 +++++++-- src/dfi/validation.h | 3 + src/util/strencodings.cpp | 4 + src/util/strencodings.h | 2 + .../feature_future_swap_limitation.py | 306 ++++++++++++++++++ test/functional/test_runner.py | 1 + 13 files changed, 657 insertions(+), 38 deletions(-) create mode 100755 test/functional/feature_future_swap_limitation.py diff --git a/src/dfi/consensus/smartcontracts.cpp b/src/dfi/consensus/smartcontracts.cpp index 70ed08b85a..033b19e8fc 100644 --- a/src/dfi/consensus/smartcontracts.cpp +++ b/src/dfi/consensus/smartcontracts.cpp @@ -9,6 +9,7 @@ #include #include #include +#include Res CSmartContractsConsensus::HandleDFIP2201Contract(const CSmartContractMessage &obj) const { const auto &consensus = txCtx.GetConsensus(); @@ -288,6 +289,46 @@ Res CSmartContractsConsensus::operator()(const CFutureSwapMessage &obj) const { return res; } } else { + if (height >= static_cast(consensus.DF23Height) && source->symbol != "DUSD") { + CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2211F, DFIPKeys::Active}; + const auto dfip11fEnabled = attributes->GetValue(activeKey, false); + const auto averageLiquidity = mnview.GetLoanTokenAverageLiquidity(obj.source.nTokenId.v); + + if (dfip11fEnabled && averageLiquidity) { + CDataStructureV0 averageKey{ + AttributeTypes::Param, ParamIDs::DFIP2211F, DFIPKeys::AverageLiquidityPercentage}; + const auto averageLiquidityPercentage = + attributes->GetValue(averageKey, DEFAULT_AVERAGE_LIQUIDITY_PERCENTAGE); + + const auto maxSwapAmount = MultiplyAmounts(*averageLiquidity, averageLiquidityPercentage); + + arith_uint256 totalSwapAmount{}; + + mnview.ForEachFuturesUserValues( + [&](const CFuturesUserKey &key, const CFuturesUserValue &futuresValues) { + if (futuresValues.source.nTokenId == obj.source.nTokenId && + futuresValues.destination == obj.destination) { + totalSwapAmount += futuresValues.source.nValue; + } + return true; + }, + {height, {}, std::numeric_limits::max()}); + + if (obj.source.nValue + totalSwapAmount > maxSwapAmount) { + auto percentageString = GetDecimalString(averageLiquidityPercentage * 100); + rtrim(percentageString, '0'); + if (percentageString.back() == '.') { + percentageString.pop_back(); + } + return Res::Err( + "Swap amount exceeds %d%% of average pool liquidity limit. Available amount to swap: %s@%s", + percentageString, + GetDecimalString((maxSwapAmount - totalSwapAmount).GetLow64()), + source->symbol); + } + } + } + if (auto res = TransferTokenBalance(obj.source.nTokenId, obj.source.nValue, obj.owner, *contractAddressValue); !res) { return res; diff --git a/src/dfi/errors.h b/src/dfi/errors.h index d06b7be2c9..883592f535 100644 --- a/src/dfi/errors.h +++ b/src/dfi/errors.h @@ -252,6 +252,8 @@ class DeFiErrors { static Res GovVarValidateFortCanningSpring() { return Res::Err("Cannot be set before FortCanningSpringHeight"); } + static Res GovVarValidateDF23Height() { return Res::Err("Cannot be set before DF23Height"); } + static Res GovVarValidateToken(const uint32_t token) { return Res::Err("No such token (%d)", token); } static Res GovVarValidateTokenExist(const uint32_t token) { return Res::Err("Token (%d) does not exist", token); } diff --git a/src/dfi/govvariables/attributes.cpp b/src/dfi/govvariables/attributes.cpp index c10e626535..c69c725737 100644 --- a/src/dfi/govvariables/attributes.cpp +++ b/src/dfi/govvariables/attributes.cpp @@ -100,6 +100,7 @@ const std::map &ATTRIBUTES::allowedParamIDs() { // Note: DFIP2206F is currently in beta testing // for testnet. May not be enabled on mainnet until testing is complete. {"dfip2206f", ParamIDs::DFIP2206F }, + {"dfip2211f", ParamIDs::DFIP2211F }, {"feature", ParamIDs::Feature }, {"foundation", ParamIDs::Foundation}, }; @@ -112,6 +113,7 @@ const std::map &ATTRIBUTES::allowedExportParamsIDs() { {ParamIDs::DFIP2203, "dfip2203" }, {ParamIDs::DFIP2206A, "dfip2206a" }, {ParamIDs::DFIP2206F, "dfip2206f" }, + {ParamIDs::DFIP2211F, "dfip2211f" }, {ParamIDs::Feature, "feature" }, {ParamIDs::Foundation, "foundation"}, }; @@ -274,6 +276,7 @@ const std::map> &ATTRIBUTES::allowedKeys {"emission-unused-fund", DFIPKeys::EmissionUnusedFund}, {"mint-tokens-to-address", DFIPKeys::MintTokens}, {"transferdomain", DFIPKeys::TransferDomain}, + {"average_liquidity_percentage", DFIPKeys::AverageLiquidityPercentage}, }}, {AttributeTypes::EVMType, { @@ -372,6 +375,7 @@ const std::map> &ATTRIBUTES::displayKeys {DFIPKeys::EmissionUnusedFund, "emission-unused-fund"}, {DFIPKeys::MintTokens, "mint-tokens-to-address"}, {DFIPKeys::TransferDomain, "transferdomain"}, + {DFIPKeys::AverageLiquidityPercentage, "average_liquidity_percentage"}, }}, {AttributeTypes::EVMType, { @@ -697,10 +701,6 @@ static ResVal VerifyEVMAuthTypes(const UniValue &array) { return {addressSet, Res::Ok()}; } -static inline void rtrim(std::string &s, unsigned char remove) { - s.erase(std::find_if(s.rbegin(), s.rend(), [&remove](unsigned char ch) { return ch != remove; }).base(), s.end()); -} - const std::map(const std::string &)>>> &ATTRIBUTES::parseValue() { static const std::map(const std::string &)>>> @@ -750,6 +750,7 @@ const std::map( {DFIPKeys::EmissionUnusedFund, VerifyBool}, {DFIPKeys::MintTokens, VerifyBool}, {DFIPKeys::TransferDomain, VerifyBool}, + {DFIPKeys::AverageLiquidityPercentage, VerifyPctInt64}, }}, {AttributeTypes::Locks, { @@ -908,6 +909,11 @@ static Res CheckValidAttrV0Key(const uint8_t type, const uint32_t typeId, const if (typeKey != DFIPKeys::DUSDInterestBurn && typeKey != DFIPKeys::DUSDLoanBurn) { return DeFiErrors::GovVarVariableUnsupportedDFIPType(typeKey); } + } else if (typeId == ParamIDs::DFIP2211F) { + if (typeKey != DFIPKeys::Active && typeKey != DFIPKeys::BlockPeriod && + typeKey != DFIPKeys::AverageLiquidityPercentage) { + return DeFiErrors::GovVarVariableUnsupportedDFIPType(typeKey); + } } else if (typeId == ParamIDs::Feature) { if (typeKey != DFIPKeys::GovUnset && typeKey != DFIPKeys::GovFoundation && typeKey != DFIPKeys::MNSetRewardAddress && typeKey != DFIPKeys::MNSetOperatorAddress && @@ -1548,7 +1554,7 @@ UniValue ATTRIBUTES::ExportFiltered(GovVarsFilter filter, const std::string &pre ret.pushKV(key, KeyBuilder(*number)); } else if (const auto amount = std::get_if(&attribute.second)) { if (attrV0->type == AttributeTypes::Param && - (attrV0->typeId == DFIP2203 || attrV0->typeId == DFIP2206F) && + (attrV0->typeId == DFIP2203 || attrV0->typeId == DFIP2206F || attrV0->typeId == DFIP2211F) && (attrV0->key == DFIPKeys::BlockPeriod || attrV0->key == DFIPKeys::StartBlock)) { ret.pushKV(key, KeyBuilder(*amount)); } else { @@ -1911,6 +1917,10 @@ Res ATTRIBUTES::Validate(const CCustomCSView &view) const { return Res::Err("Cannot be set before FortCanningSpringHeight"); } } + } else if (attrV0->typeId == ParamIDs::DFIP2211F) { + if (view.GetLastHeight() < Params().GetConsensus().DF23Height) { + return DeFiErrors::GovVarValidateDF23Height(); + } } else if (attrV0->typeId != ParamIDs::DFIP2201) { return Res::Err("Unrecognised param id"); } @@ -2177,6 +2187,41 @@ Res ATTRIBUTES::Apply(CCustomCSView &mnview, const uint32_t height) { return DeFiErrors::GovVarApplyDFIPActive("DFIP2206F"); } } + } else if (attrV0->typeId == ParamIDs::DFIP2211F) { + if (attrV0->key == DFIPKeys::Active) { + const auto value = std::get_if(&attribute.second); + if (!value) { + return DeFiErrors::GovVarApplyUnexpectedType(); + } + + if (*value) { + continue; + } + + // Disabled so delete all data to prevent stale data + // when re-enabled. Gov var TX could enable it again + // and a subsequent TX in the block could be impacted. + std::vector perBlockKeys; + mnview.ForEachTokenLiquidityPerBlock( + [&](const LoanTokenLiquidityPerBlockKey &key, const CAmount &liquidityPerBlock) { + perBlockKeys.push_back(key); + return true; + }); + + for (const auto &key : perBlockKeys) { + mnview.EraseTokenLiquidityPerBlock(key); + } + + std::set loanTokens; + mnview.ForEachTokenAverageLiquidity([&](const uint32_t id, const uint64_t liquidity) { + loanTokens.insert(id); + return true; + }); + + for (const auto &tokenID : loanTokens) { + mnview.EraseTokenAverageLiquidity(tokenID); + } + } } } else if (attrV0->type == AttributeTypes::Oracles && attrV0->typeId == OracleIDs::Splits) { diff --git a/src/dfi/govvariables/attributes.h b/src/dfi/govvariables/attributes.h index 6cb7852e0b..73e82b6cab 100644 --- a/src/dfi/govvariables/attributes.h +++ b/src/dfi/govvariables/attributes.h @@ -43,6 +43,7 @@ enum ParamIDs : uint8_t { Feature = 'h', Auction = 'i', Foundation = 'j', + DFIP2211F = 'k', }; enum OracleIDs : uint8_t { @@ -120,6 +121,7 @@ enum DFIPKeys : uint8_t { EVMEnabled = 'u', ICXEnabled = 'v', TransferDomain = 'w', + AverageLiquidityPercentage = 'x', }; enum GovernanceKeys : uint8_t { diff --git a/src/dfi/poolpairs.cpp b/src/dfi/poolpairs.cpp index c17fa287c9..05ae5acab8 100644 --- a/src/dfi/poolpairs.cpp +++ b/src/dfi/poolpairs.cpp @@ -672,6 +672,46 @@ Res CPoolPairView::SetShare(DCT_ID const &poolId, const CScript &provider, uint3 return Res::Ok(); } +bool CPoolPairView::SetLoanTokenLiquidityPerBlock(const uint32_t height, + const uint32_t &tokenId, + const CAmount liquidityPerBlock) { + return WriteBy(LoanTokenLiquidityPerBlockKey{height, tokenId}, liquidityPerBlock); +} + +bool CPoolPairView::EraseTokenLiquidityPerBlock(const LoanTokenLiquidityPerBlockKey &key) { + return EraseBy(key); +} + +void CPoolPairView::ForEachTokenLiquidityPerBlock( + std::function callback, + const LoanTokenLiquidityPerBlockKey &start) { + ForEach( + [&callback](const LoanTokenLiquidityPerBlockKey &key, const CAmount &liquidityPerBlock) { + return callback(key, liquidityPerBlock); + }, + start); +} + +bool CPoolPairView::SetLoanTokenAverageLiquidity(const uint32_t tokenId, const uint64_t liquidity) { + return WriteBy(tokenId, liquidity); +} + +std::optional CPoolPairView::GetLoanTokenAverageLiquidity(const uint32_t tokenId) { + return ReadBy(tokenId); +} + +bool CPoolPairView::EraseTokenAverageLiquidity(const uint32_t tokenId) { + return EraseBy(tokenId); +} + +void CPoolPairView::ForEachTokenAverageLiquidity( + std::function callback, + const uint32_t start) { + ForEach( + [&callback](const uint32_t &tokenId, const uint64_t &liquidity) { return callback(tokenId, liquidity); }, + start); +} + Res CPoolPairView::DelShare(DCT_ID const &poolId, const CScript &provider) { EraseBy(PoolShareKey{poolId, provider}); return Res::Ok(); diff --git a/src/dfi/poolpairs.h b/src/dfi/poolpairs.h index 5bd6f8f9f5..372931f127 100644 --- a/src/dfi/poolpairs.h +++ b/src/dfi/poolpairs.h @@ -219,6 +219,19 @@ struct PoolShareKey { } }; +struct LoanTokenLiquidityPerBlockKey { + uint32_t height; + uint32_t tokenID; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream &s, Operation ser_action) { + READWRITE(height); + READWRITE(tokenID); + } +}; + struct PoolHeightKey { DCT_ID poolID; uint32_t height; @@ -295,6 +308,18 @@ class CPoolPairView : public virtual CStorageView { std::function onTransfer, int nHeight = 0); + bool SetLoanTokenLiquidityPerBlock(const uint32_t height, const uint32_t &tokenId, const CAmount liquidityPerBlock); + bool EraseTokenLiquidityPerBlock(const LoanTokenLiquidityPerBlockKey &key); + void ForEachTokenLiquidityPerBlock( + std::function callback, + const LoanTokenLiquidityPerBlockKey &start = LoanTokenLiquidityPerBlockKey{{}, {}}); + + bool SetLoanTokenAverageLiquidity(const uint32_t tokenId, const uint64_t liquidity); + std::optional GetLoanTokenAverageLiquidity(const uint32_t tokenId); + bool EraseTokenAverageLiquidity(const uint32_t tokenId); + void ForEachTokenAverageLiquidity(std::function callback, + const uint32_t start = {}); + // tags struct ByID { static constexpr uint8_t prefix() { return 'i'; } @@ -341,6 +366,12 @@ class CPoolPairView : public virtual CStorageView { struct ByTokenDexFeePct { static constexpr uint8_t prefix() { return 'l'; } }; + struct ByLoanTokenLiquidityPerBlock { + static constexpr uint8_t prefix() { return 'p'; } + }; + struct ByLoanTokenLiquidityAverage { + static constexpr uint8_t prefix() { return '+'; } + }; }; struct CLiquidityMessage { diff --git a/src/dfi/rpc_poolpair.cpp b/src/dfi/rpc_poolpair.cpp index eda7aeef59..888aecd488 100644 --- a/src/dfi/rpc_poolpair.cpp +++ b/src/dfi/rpc_poolpair.cpp @@ -1392,19 +1392,47 @@ UniValue listpoolshares(const JSONRPCRequest &request) { return GetRPCResultCache().Set(request, ret); } +UniValue listloantokenliquidity(const JSONRPCRequest &request) { + RPCHelpMan{ + "listloantokenliquidity", + "\nReturns information about the average liquidity for loan tokens if a sufficient sample is available.\n", + {{}}, + RPCResult{"{token symbol : value } (array) JSON object with token ID and average liquidity\n"}, + RPCExamples{HelpExampleCli("listloantokenliquidity", "") + HelpExampleRpc("listloantokenliquidity", "")}, + } + .Check(request); + + if (auto res = GetRPCResultCache().TryGet(request)) { + return *res; + } + + auto view = ::GetViewSnapshot(); + + UniValue ret(UniValue::VOBJ); + view->ForEachTokenAverageLiquidity([&](const uint32_t id, const uint64_t liquidity) { + if (const auto token = view->GetToken(DCT_ID{id}); token) { + ret.pushKV(token->symbol, GetDecimalString(liquidity)); + } + return true; + }); + + return GetRPCResultCache().Set(request, ret); +} + static const CRPCCommand commands[] = { // category name actor (function) params // ------------- ----------------------- --------------------- ---------- - {"poolpair", "listpoolpairs", &listpoolpairs, {"pagination", "verbose"} }, - {"poolpair", "getpoolpair", &getpoolpair, {"key", "verbose"} }, - {"poolpair", "addpoolliquidity", &addpoolliquidity, {"from", "shareAddress", "inputs"} }, - {"poolpair", "removepoolliquidity", &removepoolliquidity, {"from", "amount", "inputs"} }, - {"poolpair", "createpoolpair", &createpoolpair, {"metadata", "inputs"} }, - {"poolpair", "updatepoolpair", &updatepoolpair, {"metadata", "inputs"} }, - {"poolpair", "poolswap", &poolswap, {"metadata", "inputs"} }, - {"poolpair", "compositeswap", &compositeswap, {"metadata", "inputs"} }, - {"poolpair", "listpoolshares", &listpoolshares, {"pagination", "verbose", "is_mine_only"}}, - {"poolpair", "testpoolswap", &testpoolswap, {"metadata", "path", "verbose"} }, + {"poolpair", "listpoolpairs", &listpoolpairs, {"pagination", "verbose"} }, + {"poolpair", "getpoolpair", &getpoolpair, {"key", "verbose"} }, + {"poolpair", "addpoolliquidity", &addpoolliquidity, {"from", "shareAddress", "inputs"} }, + {"poolpair", "removepoolliquidity", &removepoolliquidity, {"from", "amount", "inputs"} }, + {"poolpair", "createpoolpair", &createpoolpair, {"metadata", "inputs"} }, + {"poolpair", "updatepoolpair", &updatepoolpair, {"metadata", "inputs"} }, + {"poolpair", "poolswap", &poolswap, {"metadata", "inputs"} }, + {"poolpair", "compositeswap", &compositeswap, {"metadata", "inputs"} }, + {"poolpair", "listpoolshares", &listpoolshares, {"pagination", "verbose", "is_mine_only"}}, + {"poolpair", "testpoolswap", &testpoolswap, {"metadata", "path", "verbose"} }, + {"poolpair", "listloantokenliquidity", &listloantokenliquidity, {} }, }; void RegisterPoolpairRPCCommands(CRPCTable &tableRPC) { diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index a6a5da5bee..830af94af5 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -25,6 +25,8 @@ #define MILLI 0.001 +using LoanTokenCollection = std::vector>; + template static void UpdateDailyGovVariables(const std::map::const_iterator &incentivePair, CCustomCSView &cache, @@ -846,47 +848,116 @@ static void ProcessLoanEvents(const CBlockIndex *pindex, CCustomCSView &cache, c view.Flush(); } -static void ProcessFutures(const CBlockIndex *pindex, CCustomCSView &cache, const CChainParams &chainparams) { - if (pindex->nHeight < chainparams.GetConsensus().DF15FortCanningRoadHeight) { +static void LiquidityForFuturesLimit(const CBlockIndex *pindex, + CCustomCSView &cache, + const CChainParams &chainparams, + const LoanTokenCollection &loanTokens, + const bool futureSwapBlock) { + if (pindex->nHeight < chainparams.GetConsensus().DF23Height) { return; } auto attributes = cache.GetAttributes(); - CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::Active}; - CDataStructureV0 blockKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::BlockPeriod}; - CDataStructureV0 rewardKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::RewardPct}; - if (!attributes->GetValue(activeKey, false) || !attributes->CheckKey(blockKey) || - !attributes->CheckKey(rewardKey)) { + CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2211F, DFIPKeys::Active}; + if (!attributes->GetValue(activeKey, false)) { return; } - CDataStructureV0 startKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::StartBlock}; - const auto startBlock = attributes->GetValue(startKey, CAmount{}); - if (pindex->nHeight < startBlock) { + CDataStructureV0 blockKey{AttributeTypes::Param, ParamIDs::DFIP2211F, DFIPKeys::BlockPeriod}; + const auto blockPeriod = attributes->GetValue(blockKey, DEFUALT_FS_LIQUIDITY_BLOCK_PERIOD); + + const auto dusdToken = cache.GetToken("DUSD"); + if (!dusdToken) { return; } - const auto blockPeriod = attributes->GetValue(blockKey, CAmount{}); - if ((pindex->nHeight - startBlock) % blockPeriod != 0) { + const auto &dusdID = dusdToken->first; + + std::set tokens; + for (const auto &[id, loanToken] : loanTokens) { + tokens.insert(id); + } + + // Store liquidity for loan tokens + cache.ForEachPoolPair([&](const DCT_ID &, const CPoolPair &poolPair) { + // Check for loan token + const auto tokenA = tokens.count(poolPair.idTokenA); + const auto tokenB = tokens.count(poolPair.idTokenB); + if (!tokenA && !tokenB) { + return true; + } + + // Make sure this is the DUSD loan token pair + const auto dusdA = poolPair.idTokenA == dusdID; + const auto dusdB = poolPair.idTokenB == dusdID; + if (!dusdA && !dusdB) { + return true; + } + + if (tokenA) { + cache.SetLoanTokenLiquidityPerBlock(pindex->nHeight, poolPair.idTokenA.v, poolPair.reserveA); + } else { + cache.SetLoanTokenLiquidityPerBlock(pindex->nHeight, poolPair.idTokenB.v, poolPair.reserveB); + } + + return true; + }); + + // Collect old entries to delete + std::vector keysToDelete; + cache.ForEachTokenLiquidityPerBlock( + [&](const LoanTokenLiquidityPerBlockKey &key, const CAmount &liquidityPerBlock) { + if (key.height <= pindex->nHeight - blockPeriod) { + keysToDelete.push_back(key); + } else { + return false; + } + return true; + }); + + // Delete old entries + for (const auto &key : keysToDelete) { + cache.EraseTokenLiquidityPerBlock(key); + } + + if (!futureSwapBlock) { return; } - auto time = GetTimeMillis(); - LogPrintf("Future swap settlement in progress.. (height: %d)\n", pindex->nHeight); + // Get liquidity per block for each token + std::map> liquidityPerBlockByToken; + cache.ForEachTokenLiquidityPerBlock( + [&](const LoanTokenLiquidityPerBlockKey &key, const CAmount &liquidityPerBlock) { + liquidityPerBlockByToken[key.tokenID].push_back(liquidityPerBlock); + return true; + }, + {static_cast(pindex->nHeight - blockPeriod)}); - const auto rewardPct = attributes->GetValue(rewardKey, CAmount{}); - const auto discount{COIN - rewardPct}; - const auto premium{COIN + rewardPct}; + // Calculate average liquidity for each token + for (const auto &[tokenId, liquidityPerBlock] : liquidityPerBlockByToken) { + if (liquidityPerBlock.size() < blockPeriod) { + cache.EraseTokenAverageLiquidity(tokenId); + continue; + } - std::map futuresPrices; - CDataStructureV0 tokenKey{AttributeTypes::Token, 0, TokenKeys::DFIP2203Enabled}; + arith_uint256 tokenTotal{}; + for (const auto &liquidity : liquidityPerBlock) { + tokenTotal += liquidity; + } + + const auto tokenAverage = tokenTotal / blockPeriod; + cache.SetLoanTokenAverageLiquidity(tokenId, tokenAverage.GetLow64()); + } +} - std::vector> loanTokens; +static auto GetLoanTokensForFutures(CCustomCSView &cache, ATTRIBUTES attributes) { + LoanTokenCollection loanTokens; + CDataStructureV0 tokenKey{AttributeTypes::Token, 0, TokenKeys::DFIP2203Enabled}; cache.ForEachLoanToken([&](const DCT_ID &id, const CLoanView::CLoanSetLoanTokenImpl &loanToken) { tokenKey.typeId = id.v; - const auto enabled = attributes->GetValue(tokenKey, true); + const auto enabled = attributes.GetValue(tokenKey, true); if (!enabled) { return true; } @@ -897,14 +968,14 @@ static void ProcessFutures(const CBlockIndex *pindex, CCustomCSView &cache, cons }); if (loanTokens.empty()) { - attributes->ForEach( + attributes.ForEach( [&](const CDataStructureV0 &attr, const CAttributeValue &) { if (attr.type != AttributeTypes::Token) { return false; } tokenKey.typeId = attr.typeId; - const auto enabled = attributes->GetValue(tokenKey, true); + const auto enabled = attributes.GetValue(tokenKey, true); if (!enabled) { return true; } @@ -921,6 +992,49 @@ static void ProcessFutures(const CBlockIndex *pindex, CCustomCSView &cache, cons CDataStructureV0{AttributeTypes::Token}); } + return loanTokens; +} + +static void ProcessFutures(const CBlockIndex *pindex, CCustomCSView &cache, const CChainParams &chainparams) { + if (pindex->nHeight < chainparams.GetConsensus().DF15FortCanningRoadHeight) { + return; + } + + auto attributes = cache.GetAttributes(); + + CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::Active}; + CDataStructureV0 blockKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::BlockPeriod}; + CDataStructureV0 rewardKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::RewardPct}; + if (!attributes->GetValue(activeKey, false) || !attributes->CheckKey(blockKey) || + !attributes->CheckKey(rewardKey)) { + return; + } + + CDataStructureV0 startKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::StartBlock}; + const auto startBlock = attributes->GetValue(startKey, CAmount{}); + if (pindex->nHeight < startBlock) { + return; + } + + const auto loanTokens = GetLoanTokensForFutures(cache, *attributes); + const auto blockPeriod = attributes->GetValue(blockKey, CAmount{}); + const auto futureSwapBlock = (pindex->nHeight - startBlock) % blockPeriod == 0; + + LiquidityForFuturesLimit(pindex, cache, chainparams, loanTokens, futureSwapBlock); + + if (!futureSwapBlock) { + return; + } + + auto time = GetTimeMillis(); + LogPrintf("Future swap settlement in progress.. (height: %d)\n", pindex->nHeight); + + const auto rewardPct = attributes->GetValue(rewardKey, CAmount{}); + const auto discount{COIN - rewardPct}; + const auto premium{COIN + rewardPct}; + + std::map futuresPrices; + for (const auto &[id, loanToken] : loanTokens) { const auto useNextPrice{false}, requireLivePrice{true}; const auto discountPrice = diff --git a/src/dfi/validation.h b/src/dfi/validation.h index a599b3b9c0..d05e26c153 100644 --- a/src/dfi/validation.h +++ b/src/dfi/validation.h @@ -15,6 +15,9 @@ class CCoinsViewCache; class CVaultAssets; class CCustomCSView; +constexpr CAmount DEFUALT_FS_LIQUIDITY_BLOCK_PERIOD = 28 * 2880; +constexpr CAmount DEFAULT_AVERAGE_LIQUIDITY_PERCENTAGE = COIN / 10; + using CreationTxs = std::map>>>; void ProcessDeFiEvent(const CBlock &block, diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 03ad0fd56b..151db07bd5 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -578,3 +578,7 @@ std::string trim_ws(std::string const & str) size_t last = str.find_last_not_of(ws); return str.substr(first, (last - first + 1)); } + +void rtrim(std::string &s, unsigned char remove) { + s.erase(std::find_if(s.rbegin(), s.rend(), [&remove](unsigned char ch) { return ch != remove; }).base(), s.end()); +} diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 39e179638f..a48e67ed1a 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -40,6 +40,8 @@ std::vector ParseHex(const char* psz); std::vector ParseHex(const std::string& str); signed char HexDigit(char c); std::string trim_ws(std::string const & str); +void rtrim(std::string &s, unsigned char remove); + /* Returns true if each character in str is a hex character, and has an even * number of hex digits.*/ bool IsHex(const std::string& str); diff --git a/test/functional/feature_future_swap_limitation.py b/test/functional/feature_future_swap_limitation.py new file mode 100755 index 0000000000..833c4585c7 --- /dev/null +++ b/test/functional/feature_future_swap_limitation.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2019 The Bitcoin Core developers +# Copyright (c) DeFi Blockchain Developers +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. +"""Test future swap limitation""" + +from test_framework.test_framework import DefiTestFramework +from test_framework.util import assert_equal, assert_raises_rpc_error + +import time + + +class MigrateV1Test(DefiTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + self.extra_args = [ + [ + "-txnotokens=0", + "-subsidytest=1", + "-amkheight=1", + "-bayfrontheight=1", + "-dakotaheight=1", + "-eunosheight=1", + "-fortcanningheight=1", + "-fortcanninghillheight=1", + "-fortcanningroadheight=1", + "-fortcanningcrunchheight=1", + "-fortcanningspringheight=1", + "-fortcanninggreatworldheight=1", + "-fortcanningepilogueheight=1", + "-grandcentralheight=1", + "-metachainheight=100", + "-df23height=150", + ], + ] + + def run_test(self): + # Run setup + self.setup() + + # Test future swap limitation + self.test_future_swap_limitation() + + # Test wiping of data on disable + self.test_wiping_data() + + def setup(self): + # Define address + self.address = self.nodes[0].get_genesis_keys().ownerAuthAddress + + # Generate chain + self.nodes[0].generate(105) + + # Setup oracles + self.setup_oracles() + + # Setup tokens + self.setup_tokens() + + # Setup Gov vars + self.setup_govvars() + + # Setup Pool + self.setup_pool() + + def setup_oracles(self): + # Price feeds + price_feed = [ + {"currency": "USD", "token": "DFI"}, + {"currency": "USD", "token": "META"}, + {"currency": "USD", "token": "DUSD"}, + ] + + # Appoint oracle + oracle_address = self.nodes[0].getnewaddress("", "legacy") + self.oracle = self.nodes[0].appointoracle(oracle_address, price_feed, 10) + self.nodes[0].generate(1) + + # Set Oracle prices + oracle_prices = [ + {"currency": "USD", "tokenAmount": "1@DFI"}, + {"currency": "USD", "tokenAmount": "1@META"}, + {"currency": "USD", "tokenAmount": "1@DUSD"}, + ] + self.nodes[0].setoracledata(self.oracle, int(time.time()), oracle_prices) + self.nodes[0].generate(10) + + def setup_tokens(self): + # Set loan tokens + self.nodes[0].setloantoken( + { + "symbol": "META", + "name": "Meta", + "fixedIntervalPriceId": "META/USD", + "isDAT": True, + "interest": 0, + } + ) + self.nodes[0].generate(1) + + self.nodes[0].setloantoken( + { + "symbol": "DUSD", + "name": "DUSD", + "fixedIntervalPriceId": "DUSD/USD", + "mintable": True, + "interest": 0, + } + ) + self.nodes[0].generate(1) + + # Set collateral token + self.nodes[0].setcollateraltoken( + { + "token": "DFI", + "factor": 1, + "fixedIntervalPriceId": "DFI/USD", + } + ) + self.nodes[0].generate(1) + + # Mint tokens + self.nodes[0].minttokens(["1000@META"]) + self.nodes[0].minttokens(["1000@DUSD"]) + self.nodes[0].generate(1) + + # Create account DFI + self.nodes[0].utxostoaccount({self.address: "1000@DFI"}) + self.nodes[0].generate(1) + + def setup_govvars(self): + # Activate EVM and transfer domain + self.nodes[0].setgov( + { + "ATTRIBUTES": { + "v0/params/dfip2203/start_block": "150", + "v0/params/dfip2203/reward_pct": "0.05", + "v0/params/dfip2203/block_period": "20", + } + } + ) + self.nodes[0].generate(1) + + # Fully enable DFIP2203 + self.nodes[0].setgov({"ATTRIBUTES": {"v0/params/dfip2203/active": "true"}}) + self.nodes[0].generate(1) + + # Verify Gov vars + result = self.nodes[0].getgov("ATTRIBUTES")["ATTRIBUTES"] + assert_equal(result["v0/params/dfip2203/active"], "true") + assert_equal(result["v0/params/dfip2203/reward_pct"], "0.05") + assert_equal(result["v0/params/dfip2203/fee_pct"], "0.05") + assert_equal(result["v0/params/dfip2203/block_period"], "20") + + def setup_pool(self): + # Create pool pair + self.nodes[0].createpoolpair( + { + "tokenA": "META", + "tokenB": "DUSD", + "commission": 0, + "status": True, + "ownerAddress": self.address, + "pairSymbol": "META-DUSD", + }, + [], + ) + self.nodes[0].generate(1) + + # Add liquidity + self.nodes[0].addpoolliquidity( + {self.address: ["100@META", "100@DUSD"]}, self.address + ) + self.nodes[0].generate(1) + + def test_future_swap_limitation(self): + # Try and set future swap limitaiton before fork height + assert_raises_rpc_error( + -32600, + "Cannot be set before DF23Height", + self.nodes[0].setgov, + { + "ATTRIBUTES": { + "v0/params/dfip2211f/active": "true", + } + }, + ) + assert_raises_rpc_error( + -32600, + "Cannot be set before DF23Height", + self.nodes[0].setgov, + { + "ATTRIBUTES": { + "v0/params/dfip2211f/average_liquidity_percentage": "0.1", + } + }, + ) + assert_raises_rpc_error( + -32600, + "Cannot be set before DF23Height", + self.nodes[0].setgov, + { + "ATTRIBUTES": { + "v0/params/dfip2211f/block_period": "20", + } + }, + ) + + # Move to fork height + self.nodes[0].generate(150 - self.nodes[0].getblockcount()) + + # Set future swap limitaiton + self.nodes[0].setgov( + { + "ATTRIBUTES": { + "v0/params/dfip2211f/active": "true", + "v0/params/dfip2211f/average_liquidity_percentage": "0.1", + "v0/params/dfip2211f/block_period": "20", + } + } + ) + self.nodes[0].generate(1) + + # Verify Gov vars + result = self.nodes[0].getgov("ATTRIBUTES")["ATTRIBUTES"] + assert_equal(result["v0/params/dfip2211f/active"], "true") + assert_equal(result["v0/params/dfip2211f/average_liquidity_percentage"], "0.1") + assert_equal(result["v0/params/dfip2211f/block_period"], "20") + + # Move to future swap event + self.nodes[0].generate(190 - self.nodes[0].getblockcount()) + + # Check liquidity data + assert_equal(self.nodes[0].listloantokenliquidity(), {"META": "100.00000000"}) + + # Try and swap above limit + assert_raises_rpc_error( + -32600, + "Swap amount exceeds 10% of average pool liquidity limit. Available amount to swap: 10.00000000@META", + self.nodes[0].futureswap, + self.address, + "10.00000001@META", + ) + + # Move midway in future swap period + self.nodes[0].generate(199 - self.nodes[0].getblockcount()) + + # Swap the max limit + self.nodes[0].futureswap(self.address, "5.00000000@META") + self.nodes[0].futureswap(self.address, "5.00000000@META") + + # Execute pool swap to change liquidity + self.nodes[0].poolswap( + { + "from": self.address, + "tokenFrom": "DUSD", + "amountFrom": 100, + "to": self.address, + "tokenTo": "META", + } + ) + self.nodes[0].generate(1) + + # Try and swap above limit + assert_raises_rpc_error( + -32600, + "Swap amount exceeds 10% of average pool liquidity limit. Available amount to swap: 0.00000000@META", + self.nodes[0].futureswap, + self.address, + "0.00000001@META", + ) + + # Move to future swap event + self.nodes[0].generate(210 - self.nodes[0].getblockcount()) + + # Check liquidity data changed + assert_equal(self.nodes[0].listloantokenliquidity(), {"META": "72.48624516"}) + + # Try and swap above new limit + assert_raises_rpc_error( + -32600, + "Swap amount exceeds 10% of average pool liquidity limit. Available amount to swap: 7.24862451@META", + self.nodes[0].futureswap, + self.address, + "7.24862452@META", + ) + + def test_wiping_data(self): + # Disable future swap limitaiton + self.nodes[0].setgov( + { + "ATTRIBUTES": { + "v0/params/dfip2211f/active": "false", + } + } + ) + self.nodes[0].generate(1) + + # Check liquidity data empty + assert_equal(self.nodes[0].listloantokenliquidity(), {}) + + +if __name__ == "__main__": + MigrateV1Test().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 7db75fd660..104b4b87f5 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -195,6 +195,7 @@ "feature_poolswap.py", "feature_split_migrate_lock.py", "feature_poolswap_composite.py", + "feature_future_swap_limitation.py", "feature_poolswap_mechanism.py", "feature_poolswap_mainnet.py", "feature_prevent_bad_tx_propagation.py", From 1307e3f9ff6b17ba900353b720a55ca00dc4850b Mon Sep 17 00:00:00 2001 From: Bushstar Date: Wed, 6 Mar 2024 08:01:47 +0000 Subject: [PATCH 02/13] Make limit bi-directional --- src/dfi/consensus/smartcontracts.cpp | 9 +++- src/dfi/govvariables/attributes.cpp | 10 ++-- src/dfi/poolpairs.cpp | 29 ++++++------ src/dfi/poolpairs.h | 45 +++++++++++++----- src/dfi/rpc_poolpair.cpp | 8 ++-- src/dfi/validation.cpp | 23 +++++++--- .../feature_future_swap_limitation.py | 46 ++++++++++++++++--- 7 files changed, 122 insertions(+), 48 deletions(-) diff --git a/src/dfi/consensus/smartcontracts.cpp b/src/dfi/consensus/smartcontracts.cpp index 033b19e8fc..95b59edba6 100644 --- a/src/dfi/consensus/smartcontracts.cpp +++ b/src/dfi/consensus/smartcontracts.cpp @@ -289,10 +289,15 @@ Res CSmartContractsConsensus::operator()(const CFutureSwapMessage &obj) const { return res; } } else { - if (height >= static_cast(consensus.DF23Height) && source->symbol != "DUSD") { + if (height >= static_cast(consensus.DF23Height) && !dfiToDUSD) { CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2211F, DFIPKeys::Active}; const auto dfip11fEnabled = attributes->GetValue(activeKey, false); - const auto averageLiquidity = mnview.GetLoanTokenAverageLiquidity(obj.source.nTokenId.v); + const auto dusdToken = mnview.GetToken("DUSD"); + if (!dusdToken) { + return Res::Err("No DUSD token defined"); + } + const auto dest = !obj.destination ? dusdToken->first.v : obj.destination; + const auto averageLiquidity = mnview.GetLoanTokenAverageLiquidity({obj.source.nTokenId.v, dest}); if (dfip11fEnabled && averageLiquidity) { CDataStructureV0 averageKey{ diff --git a/src/dfi/govvariables/attributes.cpp b/src/dfi/govvariables/attributes.cpp index c69c725737..6034de8ae1 100644 --- a/src/dfi/govvariables/attributes.cpp +++ b/src/dfi/govvariables/attributes.cpp @@ -2212,14 +2212,14 @@ Res ATTRIBUTES::Apply(CCustomCSView &mnview, const uint32_t height) { mnview.EraseTokenLiquidityPerBlock(key); } - std::set loanTokens; - mnview.ForEachTokenAverageLiquidity([&](const uint32_t id, const uint64_t liquidity) { - loanTokens.insert(id); + std::vector averageKeys; + mnview.ForEachTokenAverageLiquidity([&](const LoanTokenAverageLiquidityKey &key, const uint64_t) { + averageKeys.push_back(key); return true; }); - for (const auto &tokenID : loanTokens) { - mnview.EraseTokenAverageLiquidity(tokenID); + for (const auto &key : averageKeys) { + mnview.EraseTokenAverageLiquidity(key); } } } diff --git a/src/dfi/poolpairs.cpp b/src/dfi/poolpairs.cpp index 05ae5acab8..b4d09a55ad 100644 --- a/src/dfi/poolpairs.cpp +++ b/src/dfi/poolpairs.cpp @@ -672,10 +672,9 @@ Res CPoolPairView::SetShare(DCT_ID const &poolId, const CScript &provider, uint3 return Res::Ok(); } -bool CPoolPairView::SetLoanTokenLiquidityPerBlock(const uint32_t height, - const uint32_t &tokenId, +bool CPoolPairView::SetLoanTokenLiquidityPerBlock(const LoanTokenLiquidityPerBlockKey &key, const CAmount liquidityPerBlock) { - return WriteBy(LoanTokenLiquidityPerBlockKey{height, tokenId}, liquidityPerBlock); + return WriteBy(key, liquidityPerBlock); } bool CPoolPairView::EraseTokenLiquidityPerBlock(const LoanTokenLiquidityPerBlockKey &key) { @@ -683,7 +682,7 @@ bool CPoolPairView::EraseTokenLiquidityPerBlock(const LoanTokenLiquidityPerBlock } void CPoolPairView::ForEachTokenLiquidityPerBlock( - std::function callback, + std::function callback, const LoanTokenLiquidityPerBlockKey &start) { ForEach( [&callback](const LoanTokenLiquidityPerBlockKey &key, const CAmount &liquidityPerBlock) { @@ -692,23 +691,25 @@ void CPoolPairView::ForEachTokenLiquidityPerBlock( start); } -bool CPoolPairView::SetLoanTokenAverageLiquidity(const uint32_t tokenId, const uint64_t liquidity) { - return WriteBy(tokenId, liquidity); +bool CPoolPairView::SetLoanTokenAverageLiquidity(const LoanTokenAverageLiquidityKey &key, const uint64_t liquidity) { + return WriteBy(key, liquidity); } -std::optional CPoolPairView::GetLoanTokenAverageLiquidity(const uint32_t tokenId) { - return ReadBy(tokenId); +std::optional CPoolPairView::GetLoanTokenAverageLiquidity(const LoanTokenAverageLiquidityKey &key) { + return ReadBy(key); } -bool CPoolPairView::EraseTokenAverageLiquidity(const uint32_t tokenId) { - return EraseBy(tokenId); +bool CPoolPairView::EraseTokenAverageLiquidity(const LoanTokenAverageLiquidityKey key) { + return EraseBy(key); } void CPoolPairView::ForEachTokenAverageLiquidity( - std::function callback, - const uint32_t start) { - ForEach( - [&callback](const uint32_t &tokenId, const uint64_t &liquidity) { return callback(tokenId, liquidity); }, + std::function callback, + const LoanTokenAverageLiquidityKey start) { + ForEach( + [&callback](const LoanTokenAverageLiquidityKey &key, const uint64_t &liquidity) { + return callback(key, liquidity); + }, start); } diff --git a/src/dfi/poolpairs.h b/src/dfi/poolpairs.h index 372931f127..345788eea6 100644 --- a/src/dfi/poolpairs.h +++ b/src/dfi/poolpairs.h @@ -219,16 +219,38 @@ struct PoolShareKey { } }; +struct LoanTokenAverageLiquidityKey { + uint32_t sourceID; + uint32_t destID; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream &s, Operation ser_action) { + READWRITE(sourceID); + READWRITE(destID); + } + + bool operator<(const LoanTokenAverageLiquidityKey &other) const { + if (sourceID == other.sourceID) { + return destID < other.destID; + } + return sourceID < other.sourceID; + } +}; + struct LoanTokenLiquidityPerBlockKey { uint32_t height; - uint32_t tokenID; + uint32_t sourceID; + uint32_t destID; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(height); - READWRITE(tokenID); + READWRITE(sourceID); + READWRITE(destID); } }; @@ -308,17 +330,18 @@ class CPoolPairView : public virtual CStorageView { std::function onTransfer, int nHeight = 0); - bool SetLoanTokenLiquidityPerBlock(const uint32_t height, const uint32_t &tokenId, const CAmount liquidityPerBlock); + bool SetLoanTokenLiquidityPerBlock(const LoanTokenLiquidityPerBlockKey &key, const CAmount liquidityPerBlock); bool EraseTokenLiquidityPerBlock(const LoanTokenLiquidityPerBlockKey &key); void ForEachTokenLiquidityPerBlock( - std::function callback, - const LoanTokenLiquidityPerBlockKey &start = LoanTokenLiquidityPerBlockKey{{}, {}}); - - bool SetLoanTokenAverageLiquidity(const uint32_t tokenId, const uint64_t liquidity); - std::optional GetLoanTokenAverageLiquidity(const uint32_t tokenId); - bool EraseTokenAverageLiquidity(const uint32_t tokenId); - void ForEachTokenAverageLiquidity(std::function callback, - const uint32_t start = {}); + std::function callback, + const LoanTokenLiquidityPerBlockKey &start = LoanTokenLiquidityPerBlockKey{}); + + bool SetLoanTokenAverageLiquidity(const LoanTokenAverageLiquidityKey &key, const uint64_t liquidity); + std::optional GetLoanTokenAverageLiquidity(const LoanTokenAverageLiquidityKey &key); + bool EraseTokenAverageLiquidity(const LoanTokenAverageLiquidityKey key); + void ForEachTokenAverageLiquidity( + std::function callback, + const LoanTokenAverageLiquidityKey start = LoanTokenAverageLiquidityKey{}); // tags struct ByID { diff --git a/src/dfi/rpc_poolpair.cpp b/src/dfi/rpc_poolpair.cpp index 888aecd488..f85463b140 100644 --- a/src/dfi/rpc_poolpair.cpp +++ b/src/dfi/rpc_poolpair.cpp @@ -1409,9 +1409,11 @@ UniValue listloantokenliquidity(const JSONRPCRequest &request) { auto view = ::GetViewSnapshot(); UniValue ret(UniValue::VOBJ); - view->ForEachTokenAverageLiquidity([&](const uint32_t id, const uint64_t liquidity) { - if (const auto token = view->GetToken(DCT_ID{id}); token) { - ret.pushKV(token->symbol, GetDecimalString(liquidity)); + view->ForEachTokenAverageLiquidity([&](const LoanTokenAverageLiquidityKey &key, const uint64_t liquidity) { + const auto sourceToken = view->GetToken(DCT_ID{key.sourceID}); + const auto destToken = view->GetToken(DCT_ID{key.destID}); + if (sourceToken && destToken) { + ret.pushKV(sourceToken->symbol + '-' + destToken->symbol, GetDecimalString(liquidity)); } return true; }); diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index 830af94af5..6f73f50898 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -879,6 +879,9 @@ static void LiquidityForFuturesLimit(const CBlockIndex *pindex, tokens.insert(id); } + // Filter out DUSD + tokens.erase(dusdID); + // Store liquidity for loan tokens cache.ForEachPoolPair([&](const DCT_ID &, const CPoolPair &poolPair) { // Check for loan token @@ -896,9 +899,15 @@ static void LiquidityForFuturesLimit(const CBlockIndex *pindex, } if (tokenA) { - cache.SetLoanTokenLiquidityPerBlock(pindex->nHeight, poolPair.idTokenA.v, poolPair.reserveA); + cache.SetLoanTokenLiquidityPerBlock( + {static_cast(pindex->nHeight), poolPair.idTokenA.v, poolPair.idTokenB.v}, poolPair.reserveA); + cache.SetLoanTokenLiquidityPerBlock( + {static_cast(pindex->nHeight), poolPair.idTokenB.v, poolPair.idTokenA.v}, poolPair.reserveB); } else { - cache.SetLoanTokenLiquidityPerBlock(pindex->nHeight, poolPair.idTokenB.v, poolPair.reserveB); + cache.SetLoanTokenLiquidityPerBlock( + {static_cast(pindex->nHeight), poolPair.idTokenB.v, poolPair.idTokenA.v}, poolPair.reserveB); + cache.SetLoanTokenLiquidityPerBlock( + {static_cast(pindex->nHeight), poolPair.idTokenA.v, poolPair.idTokenB.v}, poolPair.reserveA); } return true; @@ -926,18 +935,18 @@ static void LiquidityForFuturesLimit(const CBlockIndex *pindex, } // Get liquidity per block for each token - std::map> liquidityPerBlockByToken; + std::map> liquidityPerBlockByToken; cache.ForEachTokenLiquidityPerBlock( [&](const LoanTokenLiquidityPerBlockKey &key, const CAmount &liquidityPerBlock) { - liquidityPerBlockByToken[key.tokenID].push_back(liquidityPerBlock); + liquidityPerBlockByToken[{key.sourceID, key.destID}].push_back(liquidityPerBlock); return true; }, {static_cast(pindex->nHeight - blockPeriod)}); // Calculate average liquidity for each token - for (const auto &[tokenId, liquidityPerBlock] : liquidityPerBlockByToken) { + for (const auto &[key, liquidityPerBlock] : liquidityPerBlockByToken) { if (liquidityPerBlock.size() < blockPeriod) { - cache.EraseTokenAverageLiquidity(tokenId); + cache.EraseTokenAverageLiquidity(key); continue; } @@ -947,7 +956,7 @@ static void LiquidityForFuturesLimit(const CBlockIndex *pindex, } const auto tokenAverage = tokenTotal / blockPeriod; - cache.SetLoanTokenAverageLiquidity(tokenId, tokenAverage.GetLow64()); + cache.SetLoanTokenAverageLiquidity(key, tokenAverage.GetLow64()); } } diff --git a/test/functional/feature_future_swap_limitation.py b/test/functional/feature_future_swap_limitation.py index 833c4585c7..79eecfcf4b 100755 --- a/test/functional/feature_future_swap_limitation.py +++ b/test/functional/feature_future_swap_limitation.py @@ -233,7 +233,7 @@ def test_future_swap_limitation(self): self.nodes[0].generate(190 - self.nodes[0].getblockcount()) # Check liquidity data - assert_equal(self.nodes[0].listloantokenliquidity(), {"META": "100.00000000"}) + assert_equal(self.nodes[0].listloantokenliquidity(), {'META-DUSD': '100.00000000', 'DUSD-META': '100.00000000'}) # Try and swap above limit assert_raises_rpc_error( @@ -243,14 +243,18 @@ def test_future_swap_limitation(self): self.address, "10.00000001@META", ) + assert_raises_rpc_error( + -32600, + "Swap amount exceeds 10% of average pool liquidity limit. Available amount to swap: 10.00000000@DUSD", + self.nodes[0].futureswap, + self.address, + "10.00000001@DUSD", + "META", + ) # Move midway in future swap period self.nodes[0].generate(199 - self.nodes[0].getblockcount()) - # Swap the max limit - self.nodes[0].futureswap(self.address, "5.00000000@META") - self.nodes[0].futureswap(self.address, "5.00000000@META") - # Execute pool swap to change liquidity self.nodes[0].poolswap( { @@ -263,6 +267,11 @@ def test_future_swap_limitation(self): ) self.nodes[0].generate(1) + # Swap the max limit + self.nodes[0].futureswap(self.address, "5.00000000@META") + self.nodes[0].futureswap(self.address, "5.00000000@META") + self.nodes[0].generate(1) + # Try and swap above limit assert_raises_rpc_error( -32600, @@ -276,7 +285,7 @@ def test_future_swap_limitation(self): self.nodes[0].generate(210 - self.nodes[0].getblockcount()) # Check liquidity data changed - assert_equal(self.nodes[0].listloantokenliquidity(), {"META": "72.48624516"}) + assert_equal(self.nodes[0].listloantokenliquidity(), {'META-DUSD': '72.48624516', 'DUSD-META': '155.00000000'}) # Try and swap above new limit assert_raises_rpc_error( @@ -287,6 +296,31 @@ def test_future_swap_limitation(self): "7.24862452@META", ) + # Try and swap above new limit + assert_raises_rpc_error( + -32600, + "Swap amount exceeds 10% of average pool liquidity limit. Available amount to swap: 15.50000000@DUSD", + self.nodes[0].futureswap, + self.address, + "15.50000001@DUSD", + "META", + ) + + # Swap the max limit + self.nodes[0].futureswap(self.address, "10.00000000@DUSD", "META") + self.nodes[0].futureswap(self.address, "5.50000000@DUSD", "META") + self.nodes[0].generate(1) + + # Try and swap above new limit + assert_raises_rpc_error( + -32600, + "Swap amount exceeds 10% of average pool liquidity limit. Available amount to swap: 0.00000000@DUSD", + self.nodes[0].futureswap, + self.address, + "0.00000001@DUSD", + "META", + ) + def test_wiping_data(self): # Disable future swap limitaiton self.nodes[0].setgov( From c74ad7aec81d86cee99b1b2027066d0fc7ebec8c Mon Sep 17 00:00:00 2001 From: Bushstar Date: Wed, 6 Mar 2024 08:03:04 +0000 Subject: [PATCH 03/13] Fix typo --- src/dfi/validation.cpp | 2 +- src/dfi/validation.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index 6f73f50898..1fe1c791a0 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -865,7 +865,7 @@ static void LiquidityForFuturesLimit(const CBlockIndex *pindex, } CDataStructureV0 blockKey{AttributeTypes::Param, ParamIDs::DFIP2211F, DFIPKeys::BlockPeriod}; - const auto blockPeriod = attributes->GetValue(blockKey, DEFUALT_FS_LIQUIDITY_BLOCK_PERIOD); + const auto blockPeriod = attributes->GetValue(blockKey, DEFAULT_FS_LIQUIDITY_BLOCK_PERIOD); const auto dusdToken = cache.GetToken("DUSD"); if (!dusdToken) { diff --git a/src/dfi/validation.h b/src/dfi/validation.h index d05e26c153..a6fb1fdbeb 100644 --- a/src/dfi/validation.h +++ b/src/dfi/validation.h @@ -15,7 +15,7 @@ class CCoinsViewCache; class CVaultAssets; class CCustomCSView; -constexpr CAmount DEFUALT_FS_LIQUIDITY_BLOCK_PERIOD = 28 * 2880; +constexpr CAmount DEFAULT_FS_LIQUIDITY_BLOCK_PERIOD = 28 * 2880; constexpr CAmount DEFAULT_AVERAGE_LIQUIDITY_PERCENTAGE = COIN / 10; using CreationTxs = std::map>>>; From df1bdb02392261957e2d5e629567847bbd0c993f Mon Sep 17 00:00:00 2001 From: Bushstar Date: Wed, 6 Mar 2024 08:07:31 +0000 Subject: [PATCH 04/13] Format Python --- test/functional/feature_future_swap_limitation.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/functional/feature_future_swap_limitation.py b/test/functional/feature_future_swap_limitation.py index 79eecfcf4b..4a0b46902f 100755 --- a/test/functional/feature_future_swap_limitation.py +++ b/test/functional/feature_future_swap_limitation.py @@ -233,7 +233,10 @@ def test_future_swap_limitation(self): self.nodes[0].generate(190 - self.nodes[0].getblockcount()) # Check liquidity data - assert_equal(self.nodes[0].listloantokenliquidity(), {'META-DUSD': '100.00000000', 'DUSD-META': '100.00000000'}) + assert_equal( + self.nodes[0].listloantokenliquidity(), + {"META-DUSD": "100.00000000", "DUSD-META": "100.00000000"}, + ) # Try and swap above limit assert_raises_rpc_error( @@ -285,7 +288,10 @@ def test_future_swap_limitation(self): self.nodes[0].generate(210 - self.nodes[0].getblockcount()) # Check liquidity data changed - assert_equal(self.nodes[0].listloantokenliquidity(), {'META-DUSD': '72.48624516', 'DUSD-META': '155.00000000'}) + assert_equal( + self.nodes[0].listloantokenliquidity(), + {"META-DUSD": "72.48624516", "DUSD-META": "155.00000000"}, + ) # Try and swap above new limit assert_raises_rpc_error( From 1d8529bd85abe5b4537d8cdb7495c2bcd48f838d Mon Sep 17 00:00:00 2001 From: Bushstar Date: Wed, 6 Mar 2024 11:49:43 +0000 Subject: [PATCH 05/13] Remove redundant conditional --- src/dfi/validation.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index 1fe1c791a0..0a2d3130a1 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -898,17 +898,10 @@ static void LiquidityForFuturesLimit(const CBlockIndex *pindex, return true; } - if (tokenA) { - cache.SetLoanTokenLiquidityPerBlock( - {static_cast(pindex->nHeight), poolPair.idTokenA.v, poolPair.idTokenB.v}, poolPair.reserveA); - cache.SetLoanTokenLiquidityPerBlock( - {static_cast(pindex->nHeight), poolPair.idTokenB.v, poolPair.idTokenA.v}, poolPair.reserveB); - } else { - cache.SetLoanTokenLiquidityPerBlock( - {static_cast(pindex->nHeight), poolPair.idTokenB.v, poolPair.idTokenA.v}, poolPair.reserveB); - cache.SetLoanTokenLiquidityPerBlock( - {static_cast(pindex->nHeight), poolPair.idTokenA.v, poolPair.idTokenB.v}, poolPair.reserveA); - } + cache.SetLoanTokenLiquidityPerBlock( + {static_cast(pindex->nHeight), poolPair.idTokenA.v, poolPair.idTokenB.v}, poolPair.reserveA); + cache.SetLoanTokenLiquidityPerBlock( + {static_cast(pindex->nHeight), poolPair.idTokenB.v, poolPair.idTokenA.v}, poolPair.reserveB); return true; }); From c408e1290d1d4cadfd51f60d7c3f6a35e48f6dec Mon Sep 17 00:00:00 2001 From: Bushstar Date: Wed, 6 Mar 2024 11:54:27 +0000 Subject: [PATCH 06/13] Rename test --- test/functional/feature_future_swap_limitation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/feature_future_swap_limitation.py b/test/functional/feature_future_swap_limitation.py index 4a0b46902f..c28763f574 100755 --- a/test/functional/feature_future_swap_limitation.py +++ b/test/functional/feature_future_swap_limitation.py @@ -11,7 +11,7 @@ import time -class MigrateV1Test(DefiTestFramework): +class FutureSwapLimitationTest(DefiTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True @@ -343,4 +343,4 @@ def test_wiping_data(self): if __name__ == "__main__": - MigrateV1Test().main() + FutureSwapLimitationTest().main() From 310005c78e8d6e4dc7ac13d611e29a669393730a Mon Sep 17 00:00:00 2001 From: Prasanna Loganathar Date: Wed, 20 Mar 2024 16:40:36 +0800 Subject: [PATCH 07/13] Fix remnant changes --- src/dfi/validation.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index ff4d0f04e3..6e4ae01c98 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -22,6 +22,8 @@ #include #include +#include "chainparams.h" +#include "consensus/params.h" #define MILLI 0.001 @@ -987,10 +989,10 @@ static void ProcessLoanEvents(const CBlockIndex *pindex, CCustomCSView &cache, c static void LiquidityForFuturesLimit(const CBlockIndex *pindex, CCustomCSView &cache, - const CChainParams &chainparams, + const Consensus::Params &consensus, const LoanTokenCollection &loanTokens, const bool futureSwapBlock) { - if (pindex->nHeight < chainparams.GetConsensus().DF23Height) { + if (pindex->nHeight < consensus.DF23Height) { return; } @@ -1159,7 +1161,7 @@ static void ProcessFutures(const CBlockIndex *pindex, CCustomCSView &cache, cons const auto blockPeriod = attributes->GetValue(blockKey, CAmount{}); const auto futureSwapBlock = (pindex->nHeight - startBlock) % blockPeriod == 0; - LiquidityForFuturesLimit(pindex, cache, chainparams, loanTokens, futureSwapBlock); + LiquidityForFuturesLimit(pindex, cache, consensus, loanTokens, futureSwapBlock); if (!futureSwapBlock) { return; From 0374902ee5203cfa883cc7a48ef10208cdc1191a Mon Sep 17 00:00:00 2001 From: Prasanna Loganathar Date: Wed, 20 Mar 2024 16:46:30 +0800 Subject: [PATCH 08/13] Cleanup header --- src/dfi/validation.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index 6e4ae01c98..578f20554b 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -22,8 +22,7 @@ #include #include -#include "chainparams.h" -#include "consensus/params.h" +#include #define MILLI 0.001 From 6bdc97847436960fa824fd9283a619bf900e1bbc Mon Sep 17 00:00:00 2001 From: Prasanna Loganathar Date: Wed, 20 Mar 2024 16:52:05 +0800 Subject: [PATCH 09/13] fmt --- src/dfi/validation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index 578f20554b..d108a4e9ef 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -21,8 +21,8 @@ #include #include -#include #include +#include #define MILLI 0.001 From 8fa813a5cb3c8396754a3ecb9b7242ab9c62d2f2 Mon Sep 17 00:00:00 2001 From: Niven Date: Thu, 21 Mar 2024 15:28:55 +0800 Subject: [PATCH 10/13] Add gov variable for sampling period --- src/dfi/govvariables/attributes.cpp | 5 ++++- src/dfi/govvariables/attributes.h | 3 ++- src/dfi/validation.cpp | 6 ++++++ src/dfi/validation.h | 1 + test/functional/feature_future_swap_limitation.py | 12 ++++++++++++ 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/dfi/govvariables/attributes.cpp b/src/dfi/govvariables/attributes.cpp index 057245b08f..ae7f41a1f4 100644 --- a/src/dfi/govvariables/attributes.cpp +++ b/src/dfi/govvariables/attributes.cpp @@ -278,6 +278,7 @@ const std::map> &ATTRIBUTES::allowedKeys {"emission-unused-fund", DFIPKeys::EmissionUnusedFund}, {"mint-tokens-to-address", DFIPKeys::MintTokens}, {"transferdomain", DFIPKeys::TransferDomain}, + {"liquidity_calc_sampling_period", DFIPKeys::LiquidityCalcSamplingPeriod}, {"average_liquidity_percentage", DFIPKeys::AverageLiquidityPercentage}, }}, {AttributeTypes::EVMType, @@ -378,6 +379,7 @@ const std::map> &ATTRIBUTES::displayKeys {DFIPKeys::EmissionUnusedFund, "emission-unused-fund"}, {DFIPKeys::MintTokens, "mint-tokens-to-address"}, {DFIPKeys::TransferDomain, "transferdomain"}, + {DFIPKeys::LiquidityCalcSamplingPeriod, "liquidity_calc_sampling_period"}, {DFIPKeys::AverageLiquidityPercentage, "average_liquidity_percentage"}, }}, {AttributeTypes::EVMType, @@ -754,6 +756,7 @@ const std::map( {DFIPKeys::EmissionUnusedFund, VerifyBool}, {DFIPKeys::MintTokens, VerifyBool}, {DFIPKeys::TransferDomain, VerifyBool}, + {DFIPKeys::LiquidityCalcSamplingPeriod, VerifyInt64}, {DFIPKeys::AverageLiquidityPercentage, VerifyPctInt64}, }}, {AttributeTypes::Locks, @@ -916,7 +919,7 @@ static Res CheckValidAttrV0Key(const uint8_t type, const uint32_t typeId, const } } else if (typeId == ParamIDs::DFIP2211F) { if (typeKey != DFIPKeys::Active && typeKey != DFIPKeys::BlockPeriod && - typeKey != DFIPKeys::AverageLiquidityPercentage) { + typeKey != DFIPKeys::LiquidityCalcSamplingPeriod && typeKey != DFIPKeys::AverageLiquidityPercentage) { return DeFiErrors::GovVarVariableUnsupportedDFIPType(typeKey); } } else if (typeId == ParamIDs::Feature) { diff --git a/src/dfi/govvariables/attributes.h b/src/dfi/govvariables/attributes.h index ee373be3dc..b7e1ca792b 100644 --- a/src/dfi/govvariables/attributes.h +++ b/src/dfi/govvariables/attributes.h @@ -122,7 +122,8 @@ enum DFIPKeys : uint8_t { EVMEnabled = 'u', ICXEnabled = 'v', TransferDomain = 'w', - AverageLiquidityPercentage = 'x', + LiquidityCalcSamplingPeriod = 'x', + AverageLiquidityPercentage = 'y', }; enum GovernanceKeys : uint8_t { diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index d108a4e9ef..dcf960a0dc 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -1002,6 +1002,12 @@ static void LiquidityForFuturesLimit(const CBlockIndex *pindex, return; } + CDataStructureV0 samplingKey{AttributeTypes::Param, ParamIDs::DFIP2211F, DFIPKeys::LiquidityCalcSamplingPeriod}; + const auto samplingPeriod = attributes->GetValue(samplingKey, DEFAULT_LIQUIDITY_CALC_SAMPLING_PERIOD); + if ((pindex->nHeight - consensus.DF23Height) % samplingPeriod != 0) { + return; + } + CDataStructureV0 blockKey{AttributeTypes::Param, ParamIDs::DFIP2211F, DFIPKeys::BlockPeriod}; const auto blockPeriod = attributes->GetValue(blockKey, DEFAULT_FS_LIQUIDITY_BLOCK_PERIOD); diff --git a/src/dfi/validation.h b/src/dfi/validation.h index 4ea6f12d35..2a7de5a04c 100644 --- a/src/dfi/validation.h +++ b/src/dfi/validation.h @@ -17,6 +17,7 @@ class CCustomCSView; class CVaultAssets; constexpr CAmount DEFAULT_FS_LIQUIDITY_BLOCK_PERIOD = 28 * 2880; +constexpr CAmount DEFAULT_LIQUIDITY_CALC_SAMPLING_PERIOD = 120; constexpr CAmount DEFAULT_AVERAGE_LIQUIDITY_PERCENTAGE = COIN / 10; using CreationTxs = std::map>>>; diff --git a/test/functional/feature_future_swap_limitation.py b/test/functional/feature_future_swap_limitation.py index c28763f574..156cb8ef09 100755 --- a/test/functional/feature_future_swap_limitation.py +++ b/test/functional/feature_future_swap_limitation.py @@ -187,6 +187,16 @@ def test_future_swap_limitation(self): } }, ) + assert_raises_rpc_error( + -32600, + "Cannot be set before DF23Height", + self.nodes[0].setgov, + { + "ATTRIBUTES": { + "v0/params/dfip2211f/liquidity_calc_sampling_period": "1", + } + }, + ) assert_raises_rpc_error( -32600, "Cannot be set before DF23Height", @@ -216,6 +226,7 @@ def test_future_swap_limitation(self): { "ATTRIBUTES": { "v0/params/dfip2211f/active": "true", + "v0/params/dfip2211f/liquidity_calc_sampling_period": "1", "v0/params/dfip2211f/average_liquidity_percentage": "0.1", "v0/params/dfip2211f/block_period": "20", } @@ -226,6 +237,7 @@ def test_future_swap_limitation(self): # Verify Gov vars result = self.nodes[0].getgov("ATTRIBUTES")["ATTRIBUTES"] assert_equal(result["v0/params/dfip2211f/active"], "true") + assert_equal(result["v0/params/dfip2211f/liquidity_calc_sampling_period"], "1") assert_equal(result["v0/params/dfip2211f/average_liquidity_percentage"], "0.1") assert_equal(result["v0/params/dfip2211f/block_period"], "20") From e0577fd7f854ad5c63b45a835db1c563f3e29897 Mon Sep 17 00:00:00 2001 From: Niven Date: Thu, 21 Mar 2024 15:43:26 +0800 Subject: [PATCH 11/13] Resolve parse value --- src/dfi/govvariables/attributes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dfi/govvariables/attributes.cpp b/src/dfi/govvariables/attributes.cpp index ae7f41a1f4..0aed4b6fc4 100644 --- a/src/dfi/govvariables/attributes.cpp +++ b/src/dfi/govvariables/attributes.cpp @@ -1567,7 +1567,8 @@ UniValue ATTRIBUTES::ExportFiltered(GovVarsFilter filter, const std::string &pre } else if (const auto amount = std::get_if(&attribute.second)) { if (attrV0->type == AttributeTypes::Param && (attrV0->typeId == DFIP2203 || attrV0->typeId == DFIP2206F || attrV0->typeId == DFIP2211F) && - (attrV0->key == DFIPKeys::BlockPeriod || attrV0->key == DFIPKeys::StartBlock)) { + (attrV0->key == DFIPKeys::BlockPeriod || attrV0->key == DFIPKeys::StartBlock || + attrV0->key == DFIPKeys::LiquidityCalcSamplingPeriod)) { ret.pushKV(key, KeyBuilder(*amount)); } else { auto decimalStr = GetDecimalString(*amount); From 0368a7e06668498959147e2b770f4df5cdde08d3 Mon Sep 17 00:00:00 2001 From: Bushstar Date: Thu, 21 Mar 2024 08:12:53 +0000 Subject: [PATCH 12/13] Sample a non-consecutive period --- test/functional/feature_future_swap_limitation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/feature_future_swap_limitation.py b/test/functional/feature_future_swap_limitation.py index 156cb8ef09..adf9ac8835 100755 --- a/test/functional/feature_future_swap_limitation.py +++ b/test/functional/feature_future_swap_limitation.py @@ -226,7 +226,7 @@ def test_future_swap_limitation(self): { "ATTRIBUTES": { "v0/params/dfip2211f/active": "true", - "v0/params/dfip2211f/liquidity_calc_sampling_period": "1", + "v0/params/dfip2211f/liquidity_calc_sampling_period": "2", "v0/params/dfip2211f/average_liquidity_percentage": "0.1", "v0/params/dfip2211f/block_period": "20", } @@ -237,7 +237,7 @@ def test_future_swap_limitation(self): # Verify Gov vars result = self.nodes[0].getgov("ATTRIBUTES")["ATTRIBUTES"] assert_equal(result["v0/params/dfip2211f/active"], "true") - assert_equal(result["v0/params/dfip2211f/liquidity_calc_sampling_period"], "1") + assert_equal(result["v0/params/dfip2211f/liquidity_calc_sampling_period"], "2") assert_equal(result["v0/params/dfip2211f/average_liquidity_percentage"], "0.1") assert_equal(result["v0/params/dfip2211f/block_period"], "20") From 5af1789ccffb1afdcd151736c05697095dcfa160 Mon Sep 17 00:00:00 2001 From: Bushstar Date: Thu, 21 Mar 2024 16:45:05 +0000 Subject: [PATCH 13/13] Update required sample for block sample size --- src/dfi/validation.cpp | 5 +++-- test/functional/feature_future_swap_limitation.py | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index 22a0335b5a..4c6391ac12 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -1081,8 +1081,9 @@ static void LiquidityForFuturesLimit(const CBlockIndex *pindex, {static_cast(pindex->nHeight - blockPeriod)}); // Calculate average liquidity for each token + const auto expectedEntries = blockPeriod / samplingPeriod; for (const auto &[key, liquidityPerBlock] : liquidityPerBlockByToken) { - if (liquidityPerBlock.size() < blockPeriod) { + if (liquidityPerBlock.size() < expectedEntries) { cache.EraseTokenAverageLiquidity(key); continue; } @@ -1092,7 +1093,7 @@ static void LiquidityForFuturesLimit(const CBlockIndex *pindex, tokenTotal += liquidity; } - const auto tokenAverage = tokenTotal / blockPeriod; + const auto tokenAverage = tokenTotal / expectedEntries; cache.SetLoanTokenAverageLiquidity(key, tokenAverage.GetLow64()); } } diff --git a/test/functional/feature_future_swap_limitation.py b/test/functional/feature_future_swap_limitation.py index adf9ac8835..54380b80f1 100755 --- a/test/functional/feature_future_swap_limitation.py +++ b/test/functional/feature_future_swap_limitation.py @@ -302,31 +302,31 @@ def test_future_swap_limitation(self): # Check liquidity data changed assert_equal( self.nodes[0].listloantokenliquidity(), - {"META-DUSD": "72.48624516", "DUSD-META": "155.00000000"}, + {"META-DUSD": "69.98499472", "DUSD-META": "160.00000000"}, ) # Try and swap above new limit assert_raises_rpc_error( -32600, - "Swap amount exceeds 10% of average pool liquidity limit. Available amount to swap: 7.24862451@META", + "Swap amount exceeds 10% of average pool liquidity limit. Available amount to swap: 6.99849947@META", self.nodes[0].futureswap, self.address, - "7.24862452@META", + "6.99849948@META", ) # Try and swap above new limit assert_raises_rpc_error( -32600, - "Swap amount exceeds 10% of average pool liquidity limit. Available amount to swap: 15.50000000@DUSD", + "Swap amount exceeds 10% of average pool liquidity limit. Available amount to swap: 16.00000000@DUSD", self.nodes[0].futureswap, self.address, - "15.50000001@DUSD", + "16.00000001@DUSD", "META", ) # Swap the max limit self.nodes[0].futureswap(self.address, "10.00000000@DUSD", "META") - self.nodes[0].futureswap(self.address, "5.50000000@DUSD", "META") + self.nodes[0].futureswap(self.address, "6.00000000@DUSD", "META") self.nodes[0].generate(1) # Try and swap above new limit