diff --git a/src/masternodes/poolpairs.cpp b/src/masternodes/poolpairs.cpp index a11c17c155..22bc6967c9 100644 --- a/src/masternodes/poolpairs.cpp +++ b/src/masternodes/poolpairs.cpp @@ -218,6 +218,27 @@ void ReadValueMoveToNext(TIterator & it, DCT_ID poolId, ValueType & value, uint3 } } +template +void FindSuitablePoolRewards(TIterator & it, PoolHeightKey poolKey, uint32_t endHeight, ValueType & value, uint32_t & height) { + + static const auto poolStartHeight = uint32_t(Params().GetConsensus().FortCanningHillHeight); + poolKey.height = std::max(poolKey.height, poolStartHeight); + + auto maxHeight = 0u; + while ((!it.Valid() || it.Key().poolID != poolKey.poolID) && poolKey.height < endHeight) { + maxHeight = poolKey.height; + it.Seek(poolKey); + poolKey.height++; + } + + if (it.Valid() && it.Key().poolID == poolKey.poolID) { + value = it.Value(); + height = std::max(maxHeight, it.Key().height); + } else { + height = UINT_MAX; + } +} + void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function onLiquidity, uint32_t begin, uint32_t end, std::function onReward) { if (begin >= end) { return; @@ -231,27 +252,32 @@ void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function(poolKey); + FindSuitablePoolRewards(itPoolReward, poolKey, end, poolReward, startPoolReward); + auto nextPoolReward = startPoolReward; + + CAmount poolLoanReward = 0; + auto startPoolLoanReward = begin; auto itPoolLoanReward = LowerBound(poolKey); + FindSuitablePoolRewards(itPoolLoanReward, poolKey, end, poolLoanReward, startPoolLoanReward); + auto nextPoolLoanReward = startPoolLoanReward; CAmount totalLiquidity = 0; auto nextTotalLiquidity = begin; auto itTotalLiquidity = LowerBound(poolKey); CBalances customRewards; - auto nextCustomRewards = begin; + auto startCustomRewards = begin; auto itCustomRewards = LowerBound(poolKey); + FindSuitablePoolRewards(itCustomRewards, poolKey, end, customRewards, startCustomRewards); + auto nextCustomRewards = startCustomRewards; PoolSwapValue poolSwap; auto nextPoolSwap = UINT_MAX; auto poolSwapHeight = UINT_MAX; auto itPoolSwap = LowerBound(poolKey); - if (itPoolSwap.Valid() && itPoolSwap.Key().poolID == poolId) { - nextPoolSwap = itPoolSwap.Key().height; - } + FindSuitablePoolRewards(itPoolSwap, poolKey, end, poolSwap, nextPoolSwap); for (auto height = begin; height < end;) { // find suitable pool liquidity @@ -276,7 +302,7 @@ void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function= startPoolReward && poolReward != 0) { CAmount providerReward = 0; if (height < newCalcHeight) { // old calculation uint32_t liqWeight = liquidity * PRECISION / totalLiquidity; @@ -286,7 +312,7 @@ void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function= startPoolLoanReward && poolLoanReward != 0) { CAmount providerReward = liquidityReward(poolLoanReward, liquidity, totalLiquidity); onReward(RewardType::LoanTokenDEXReward, {DCT_ID{0}, providerReward}, height); } @@ -309,9 +335,11 @@ void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function= startCustomRewards) { + for (const auto& reward : customRewards.balances) { + if (auto providerReward = liquidityReward(reward.second, liquidity, totalLiquidity)) { + onReward(RewardType::Pool, {reward.first, providerReward}, height); + } } } ++height; diff --git a/src/test/liquidity_tests.cpp b/src/test/liquidity_tests.cpp index ddbe0eafbd..a78317cdf4 100644 --- a/src/test/liquidity_tests.cpp +++ b/src/test/liquidity_tests.cpp @@ -404,7 +404,7 @@ BOOST_AUTO_TEST_CASE(owner_rewards) return true; }); - mnview.SetDailyReward(3, COIN); + mnview.SetDailyReward(4, COIN); auto oldRewardCalculation = [](CAmount liquidity, const CPoolPair& pool) -> CAmount { constexpr const uint32_t PRECISION = 10000; @@ -420,6 +420,14 @@ BOOST_AUTO_TEST_CASE(owner_rewards) return std::make_pair(feeA, feeB); }; + // fixes the bug + const int bugFixHeight = 7; // < 10 + const_cast(Params().GetConsensus().FortCanningHillHeight) = bugFixHeight; + + int rewardsCount = 0; + int commissionACount = 0; + int commissionBCount = 0; + mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) { auto onLiquidity = [&]() -> CAmount { return mnview.GetBalance(shareAddress[0], idPool).nValue; @@ -428,12 +436,15 @@ BOOST_AUTO_TEST_CASE(owner_rewards) [&](RewardType type, CTokenAmount amount, uint32_t height) { switch(type) { case RewardType::Coinbase: + rewardsCount++; BOOST_CHECK_EQUAL(amount.nValue, oldRewardCalculation(onLiquidity(), pool)); break; case RewardType::Commission: if (amount.nTokenId == pool.idTokenA) { + commissionACount++; BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(onLiquidity(), pool).first); } else { + commissionBCount++; BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(onLiquidity(), pool).second); } break; @@ -446,8 +457,12 @@ BOOST_AUTO_TEST_CASE(owner_rewards) return true; }); + BOOST_CHECK_EQUAL(rewardsCount, 10 * (10 - bugFixHeight)); + BOOST_CHECK_EQUAL(commissionACount, 10); + BOOST_CHECK_EQUAL(commissionBCount, 10); + // new calculation - const_cast(Params().GetConsensus().BayfrontGardensHeight) = 6; + const_cast(Params().GetConsensus().BayfrontGardensHeight) = 8; auto newRewardCalculation = [](CAmount liquidity, const CPoolPair& pool) -> CAmount { return COIN / 2880 * pool.rewardPct / COIN * liquidity / pool.totalLiquidity; @@ -461,12 +476,21 @@ BOOST_AUTO_TEST_CASE(owner_rewards) mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) { pool.swapEvent = true; + mnview.SetPoolPair(idPool, 8, pool); pool.ownerAddress = shareAddress[1]; pool.rewards = CBalances{TAmounts{{DCT_ID{idPool.v+1}, COIN}}}; - mnview.SetPoolPair(idPool, 8, pool); + mnview.UpdatePoolPair(idPool, 8, pool.status, pool.commission, pool.ownerAddress, pool.rewards); return false; }); + int oldRewardsCount = 0; + int newRewardsCount = 0; + int poolRewardsCount = 0; + int oldCommissionACount = 0; + int newCommissionACount = 0; + int oldCommissionBCount = 0; + int newCommissionBCount = 0; + mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) { auto onLiquidity = [&] () -> CAmount { return mnview.GetBalance(shareAddress[1], idPool).nValue; @@ -476,22 +500,29 @@ BOOST_AUTO_TEST_CASE(owner_rewards) if (height >= Params().GetConsensus().BayfrontGardensHeight) { if (type == RewardType::Pool) { for (const auto& reward : pool.rewards.balances) { + poolRewardsCount++; auto providerReward = static_cast((arith_uint256(reward.second) * arith_uint256(onLiquidity()) / arith_uint256(pool.totalLiquidity)).GetLow64()); BOOST_CHECK_EQUAL(amount.nValue, providerReward); } } else if (type == RewardType::Coinbase) { + newRewardsCount++; BOOST_CHECK_EQUAL(amount.nValue, newRewardCalculation(onLiquidity(), pool)); } else if (amount.nTokenId == pool.idTokenA) { + newCommissionACount++; BOOST_CHECK_EQUAL(amount.nValue, newCommissionCalculation(onLiquidity(), pool).first); } else { + newCommissionBCount++; BOOST_CHECK_EQUAL(amount.nValue, newCommissionCalculation(onLiquidity(), pool).second); } } else { if (type & RewardType::Rewards) { + oldRewardsCount++; BOOST_CHECK_EQUAL(amount.nValue, oldRewardCalculation(onLiquidity(), pool)); } else if (amount.nTokenId == pool.idTokenA) { + oldCommissionACount++; BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(onLiquidity(), pool).first); } else { + oldCommissionBCount++; BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(onLiquidity(), pool).second); } } @@ -500,6 +531,14 @@ BOOST_AUTO_TEST_CASE(owner_rewards) ); return false; }); + + BOOST_CHECK_EQUAL(oldRewardsCount, 1); + BOOST_CHECK_EQUAL(newRewardsCount, 2); + BOOST_CHECK_EQUAL(poolRewardsCount, 2); + BOOST_CHECK_EQUAL(oldCommissionACount, 1); + BOOST_CHECK_EQUAL(newCommissionACount, 1); + BOOST_CHECK_EQUAL(oldCommissionBCount, 1); + BOOST_CHECK_EQUAL(newCommissionBCount, 1); } BOOST_AUTO_TEST_SUITE_END()