From 794e093a75a1d007d74e2378f527d513685cc3b6 Mon Sep 17 00:00:00 2001 From: Anthony Fieroni Date: Mon, 29 Mar 2021 09:04:02 +0300 Subject: [PATCH] Update rewards on every block as drop-in replacement Signed-off-by: Anthony Fieroni --- src/masternodes/masternodes.cpp | 15 +- src/masternodes/poolpairs.cpp | 247 ++++++++++++++++++++++++------- src/masternodes/poolpairs.h | 48 +----- src/masternodes/rpc_accounts.cpp | 2 +- src/test/liquidity_tests.cpp | 9 +- src/validation.cpp | 24 ++- 6 files changed, 222 insertions(+), 123 deletions(-) diff --git a/src/masternodes/masternodes.cpp b/src/masternodes/masternodes.cpp index a822fbc9a90..43d887367e5 100644 --- a/src/masternodes/masternodes.cpp +++ b/src/masternodes/masternodes.cpp @@ -755,20 +755,7 @@ bool CCustomCSView::CalculateOwnerRewards(CScript const & owner, uint32_t target }; auto beginHeight = std::max(*height, balanceHeight); CalculatePoolRewards(poolId, onLiquidity, beginHeight, targetHeight, - [&](CScript const & from, uint8_t type, CTokenAmount amount, uint32_t height) { - if (!from.empty()) { - auto res = SubBalance(from, amount); - if (!res) { - LogPrintf("Custom pool rewards: can't subtract balance of %s: %s, height %ld\n", from.GetHex(), res.msg, targetHeight); - return; // no funds, no rewards - } - } else if (type != uint8_t(RewardType::Commission)) { - auto res = SubCommunityBalance(CommunityAccountType::IncentiveFunding, amount.nValue); - if (!res) { - LogPrintf("Pool rewards: can't subtract community balance: %s, height %ld\n", res.msg, targetHeight); - return; - } - } + [&](uint8_t, CTokenAmount amount, uint32_t height) { auto res = AddBalance(owner, amount); if (!res) { LogPrintf("Pool rewards: can't update balance of %s: %s, height %ld\n", owner.GetHex(), res.msg, targetHeight); diff --git a/src/masternodes/poolpairs.cpp b/src/masternodes/poolpairs.cpp index 18003966bbf..e0eb51c01dd 100644 --- a/src/masternodes/poolpairs.cpp +++ b/src/masternodes/poolpairs.cpp @@ -13,6 +13,53 @@ const unsigned char CPoolPairView::ByShare ::prefix = 'k'; const unsigned char CPoolPairView::ByDailyReward ::prefix = 'I'; const unsigned char CPoolPairView::ByPoolHeight ::prefix = 'P'; +struct PoolHeightValue { + DCT_ID idTokenA{}; + DCT_ID idTokenB{}; + CBalances rewards; + bool swapEvent = false; + CAmount blockReward = 0; + CAmount totalLiquidity = 0; + CAmount blockCommissionA = 0; + CAmount blockCommissionB = 0; + + PoolHeightValue() = default; + + CAmount poolRewardPerBlock(CAmount dailyReward, CAmount rewardPct) { + return dailyReward / Params().GetConsensus().blocksPerDay() * rewardPct / COIN; + } + + PoolHeightValue(CPoolPair const & pool, CAmount dailyReward) { + rewards = pool.rewards; + idTokenA = pool.idTokenA; + idTokenB = pool.idTokenB; + swapEvent = pool.swapEvent; + blockReward = poolRewardPerBlock(dailyReward, pool.rewardPct); + totalLiquidity = pool.totalLiquidity; + blockCommissionA = pool.blockCommissionA; + blockCommissionB = pool.blockCommissionB; + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(rewards); + READWRITE(idTokenA); + READWRITE(idTokenB); + READWRITE(swapEvent); + READWRITE(blockReward); + READWRITE(totalLiquidity); + READWRITE(blockCommissionA); + READWRITE(blockCommissionB); + } +}; + +template +inline bool IterReadKey(std::unique_ptr & it, T & dbKey, DCT_ID const & poolId) { + return it->Valid() && CPoolPairView::ReadDbKey(it->Key(), dbKey) && dbKey.second.poolID == poolId; +} + Res CPoolPairView::SetPoolPair(DCT_ID const & poolId, uint32_t height, CPoolPair const & pool) { if (pool.idTokenA == pool.idTokenB) { @@ -26,13 +73,22 @@ Res CPoolPairView::SetPoolPair(DCT_ID const & poolId, uint32_t height, CPoolPair return Res::Err("Error, there is already a poolpairwith same tokens, but different poolId"); } + auto dailyReward = [&]() -> CAmount { + auto dbPct = MakeDbKey(PoolHeightKey{{}, height}); + auto itPct = LowerBound(dbPct); + if (!IterReadKey(itPct, dbPct, DCT_ID{})) { + return 0; + } + return ReadValue(itPct->Value()); + }; + // create new if (!poolPairByID && !poolPairByTokens) { WriteBy(poolId, pool); WriteBy(ByPairKey{pool.idTokenA, pool.idTokenB}, poolId); WriteBy(ByPairKey{pool.idTokenB, pool.idTokenA}, poolId); if (height < UINT_MAX) { - WriteBy(PoolHeightKey{poolId, height}, pool); + WriteBy(PoolHeightKey{poolId, height}, PoolHeightValue(pool, dailyReward())); } return Res::Ok(); } @@ -43,7 +99,7 @@ Res CPoolPairView::SetPoolPair(DCT_ID const & poolId, uint32_t height, CPoolPair && poolPairByTokens->second.idTokenB == pool.idTokenB) { WriteBy(poolId, pool); if (height < UINT_MAX) { - WriteBy(PoolHeightKey{poolId, height}, pool); + WriteBy(PoolHeightKey{poolId, height}, PoolHeightValue(pool, dailyReward())); } return Res::Ok(); } @@ -122,71 +178,38 @@ boost::optional > CPoolPairView::GetPoolPair(const return {}; } -template -inline bool IterReadKey(std::unique_ptr & it, T & dbKey, DCT_ID const & poolId) { - return it->Valid() && CPoolPairView::ReadDbKey(it->Key(), dbKey) && dbKey.second.poolID == poolId; +inline CAmount liquidityReward(CAmount reward, CAmount liquidity, CAmount totalLiquidity) { + return static_cast((arith_uint256(reward) * arith_uint256(liquidity) / arith_uint256(totalLiquidity)).GetLow64()); } -template -inline uint32_t IterReadKeyHeight(std::unique_ptr & it, T & dbKey, DCT_ID const & poolId) { - return IterReadKey(it, dbKey, poolId) ? dbKey.second.height : UINT_MAX; -} - -inline CAmount poolRewardPerBlock(CAmount dailyReward, CAmount rewardPct) { - return dailyReward / Params().GetConsensus().blocksPerDay() * rewardPct / COIN; -} - -void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function onLiquidity, uint32_t begin, uint32_t end, std::function onReward) { +void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function onLiquidity, uint32_t begin, uint32_t end, std::function onReward) { if (begin >= end) { return; } constexpr const uint32_t PRECISION = 10000; const auto newCalcHeight = uint32_t(Params().GetConsensus().BayfrontGardensHeight); /// @Note we store keys in desc oreder so Prev is actually go in forward - auto dbPct = MakeDbKey(PoolHeightKey{{}, begin}); auto dbPool = MakeDbKey(PoolHeightKey{poolId, begin}); - auto itPct = LowerBound(dbPct); auto itPool = LowerBound(dbPool); - CAmount dailyReward = 0; - auto dailyBeginHeight = UINT_MAX; - while (!IterReadKey(itPct, dbPct, DCT_ID{}) && dbPct.second.height < end) { - dbPct.second.height++; - itPct->Seek(DbTypeToBytes(dbPct)); - } - if (dbPct.second.height < end) { - dailyReward = ReadValue(itPct->Value()); - dailyBeginHeight = dbPct.second.height; - itPct->Prev(); - } - auto dailyEndHeight = IterReadKeyHeight(itPct, dbPct, DCT_ID{}); while (IterReadKey(itPool, dbPool, poolId)) { // rewards starting in same block as pool const auto poolHeight = dbPool.second.height; - const auto pool = ReadValue(itPool->Value()); - auto poolReward = poolRewardPerBlock(dailyReward, pool.rewardPct); + const auto pool = ReadValue(itPool->Value()); + const auto poolReward = pool.blockReward; itPool->Prev(); - auto endHeight = std::min(end, IterReadKeyHeight(itPool, dbPool, poolId)); - for (auto height = begin; height < endHeight; height++) { - if (height == dailyEndHeight) { - dailyReward = ReadValue(itPct->Value()); - poolReward = poolRewardPerBlock(dailyReward, pool.rewardPct); - itPct->Prev(); - dailyEndHeight = IterReadKeyHeight(itPct, dbPct, DCT_ID{}); - } - if (pool.totalLiquidity == 0) { - continue; - } - auto liquidity = onLiquidity(); + auto endHeight = std::min(end, IterReadKey(itPool, dbPool, poolId) ? dbPool.second.height : UINT_MAX); + for (auto height = begin; height < endHeight && pool.totalLiquidity != 0; height++) { + const auto liquidity = onLiquidity(); // daily rewards - if (height >= dailyBeginHeight && poolReward != 0) { - CAmount providerReward; + if (poolReward != 0) { + CAmount providerReward = 0; if (height < newCalcHeight) { // old calculation uint32_t liqWeight = liquidity * PRECISION / pool.totalLiquidity; providerReward = poolReward * liqWeight / PRECISION; } else { // new calculation - providerReward = static_cast((arith_uint256(poolReward) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); + providerReward = liquidityReward(poolReward, liquidity, pool.totalLiquidity); } - onReward({}, uint8_t(RewardType::Rewards), {DCT_ID{0}, providerReward}, height); + onReward(uint8_t(RewardType::Rewards), {DCT_ID{0}, providerReward}, height); } // commissions if (height == poolHeight && pool.swapEvent) { @@ -196,16 +219,16 @@ void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function((arith_uint256(pool.blockCommissionA) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); - feeB = static_cast((arith_uint256(pool.blockCommissionB) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); + feeA = liquidityReward(pool.blockCommissionA, liquidity, pool.totalLiquidity); + feeB = liquidityReward(pool.blockCommissionB, liquidity, pool.totalLiquidity); } - onReward({}, uint8_t(RewardType::Commission), {pool.idTokenA, feeA}, height); - onReward({}, uint8_t(RewardType::Commission), {pool.idTokenB, feeB}, height); + onReward(uint8_t(RewardType::Commission), {pool.idTokenA, feeA}, height); + onReward(uint8_t(RewardType::Commission), {pool.idTokenB, feeB}, height); } // custom rewards for (const auto& reward : pool.rewards.balances) { - if (auto providerReward = static_cast((arith_uint256(reward.second) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64())) { - onReward(pool.ownerAddress, uint8_t(RewardType::Rewards), {reward.first, providerReward}, height); + if (auto providerReward = liquidityReward(reward.second, liquidity, pool.totalLiquidity)) { + onReward(uint8_t(RewardType::Rewards), {reward.first, providerReward}, height); } } } @@ -366,6 +389,118 @@ CAmount CPoolPair::slopeSwap(CAmount unswapped, CAmount &poolFrom, CAmount &pool return swapped.GetLow64(); } +CAmount CPoolPairView::UpdatePoolRewards(std::function onGetBalance, std::function onTransfer, int nHeight) { + + bool newRewardCalc = nHeight >= Params().GetConsensus().BayfrontGardensHeight; + + constexpr uint32_t const PRECISION = 10000; // (== 100%) just searching the way to avoid arith256 inflating + CAmount totalDistributed = 0; + + ForEachPoolPair([&] (DCT_ID const & poolId, CPoolPair pool) { + + CAmount distributedFeeA = 0; + CAmount distributedFeeB = 0; + + uint32_t height = nHeight; + PoolHeightValue poolHeight; + auto dbPool = MakeDbKey(PoolHeightKey{poolId, height}); + auto itPool = LowerBound(dbPool); + if (IterReadKey(itPool, dbPool, poolId)) { + poolHeight = ReadValue(itPool->Value()); + } + + const auto poolReward = poolHeight.blockReward; + + auto rewards = pool.rewards; + for (auto it = rewards.balances.begin(), next_it = it; it != rewards.balances.end(); it = next_it) { + ++next_it; + + // Get token balance + const auto balance = onGetBalance(pool.ownerAddress, it->first).nValue; + + // Make there's enough to pay reward otherwise remove it + if (balance < it->second) { + rewards.balances.erase(it); + } + } + + if (pool.totalLiquidity == 0 || (!pool.swapEvent && poolReward == 0 && rewards.balances.empty())) { + if (rewards != poolHeight.rewards) { + poolHeight.rewards = rewards; + WriteBy(PoolHeightKey{poolId, height}, poolHeight); + } + return true; // no events, skip to the next pool + } + + CBalances customRewards; + + ForEachPoolShare([&] (DCT_ID const & currentId, CScript const & provider, uint32_t) { + if (currentId != poolId) { + return false; // stop + } + CAmount const liquidity = onGetBalance(provider, poolId).nValue; + + uint32_t const liqWeight = liquidity * PRECISION / pool.totalLiquidity; + assert (liqWeight < PRECISION); + + // distribute trading fees + if (pool.swapEvent) { + if (newRewardCalc) { + distributedFeeA += liquidityReward(pool.blockCommissionA, liquidity, pool.totalLiquidity); + distributedFeeB += liquidityReward(pool.blockCommissionB, liquidity, pool.totalLiquidity); + } else { + distributedFeeA += pool.blockCommissionA * liqWeight / PRECISION; + distributedFeeB += pool.blockCommissionB * liqWeight / PRECISION; + } + } + + // distribute yield farming + if (poolReward) { + if (newRewardCalc) { + totalDistributed += liquidityReward(poolReward, liquidity, pool.totalLiquidity); + } else { + totalDistributed += poolReward * liqWeight / PRECISION; + } + } + + for (const auto& reward : rewards.balances) { + if (auto providerReward = liquidityReward(reward.second, liquidity, pool.totalLiquidity)) { + customRewards.Add({reward.first, providerReward}); + } + } + + return true; + }, PoolShareKey{poolId, CScript{}}); + + for (auto it = rewards.balances.begin(), next_it = it; it != rewards.balances.end(); it = next_it) { + ++next_it; + + // make sure owner have enough tokens + if (!onTransfer(pool.ownerAddress, {it->first, customRewards.balances[it->first]})) { + rewards.balances.erase(it); + } + } + + if (rewards != poolHeight.rewards) { + poolHeight.rewards = rewards; + WriteBy(PoolHeightKey{poolId, height}, poolHeight); + } + + if (pool.swapEvent) { + pool.blockCommissionA -= distributedFeeA; + pool.blockCommissionB -= distributedFeeB; + pool.swapEvent = false; + + auto res = SetPoolPair(poolId, UINT_MAX, pool); + if (!res.ok) { + LogPrintf("Pool rewards: can't update pool (id=%s) state: %s\n", poolId.ToString(), res.msg); + } + } + return true; + }); + return totalDistributed; +} + Res CPoolPairView::SetShare(DCT_ID const & poolId, CScript const & provider, uint32_t height) { WriteBy(PoolShareKey{poolId, provider}, height); return Res::Ok(); @@ -381,6 +516,12 @@ boost::optional CPoolPairView::GetShare(DCT_ID const & poolId, CScript } Res CPoolPairView::SetDailyReward(uint32_t height, CAmount reward) { + ForEachPoolPair([&](DCT_ID const & id, CPoolPair pool) { + if (pool.totalLiquidity != 0) { + WriteBy(PoolHeightKey{id, height}, PoolHeightValue(pool, reward)); + } + return true; + }); WriteBy(PoolHeightKey{{}, height}, reward); return Res::Ok(); } diff --git a/src/masternodes/poolpairs.h b/src/masternodes/poolpairs.h index 27e369a6e25..8b2bac1727a 100644 --- a/src/masternodes/poolpairs.h +++ b/src/masternodes/poolpairs.h @@ -210,55 +210,11 @@ class CPoolPairView : public virtual CStorageView boost::optional GetShare(DCT_ID const & poolId, CScript const & provider); - void CalculatePoolRewards(DCT_ID const & poolId, std::function onLiquidity, uint32_t begin, uint32_t end, std::function onReward); + void CalculatePoolRewards(DCT_ID const & poolId, std::function onLiquidity, uint32_t begin, uint32_t end, std::function onReward); Res SetDailyReward(uint32_t height, CAmount reward); - void UpdatePoolCommissions(std::function onGetBalance, int nHeight = 0) { - - bool newRewardCalc = nHeight >= Params().GetConsensus().BayfrontGardensHeight; - - constexpr uint32_t const PRECISION = 10000; // (== 100%) just searching the way to avoid arith256 inflating - - ForEachPoolPair([&] (DCT_ID const & poolId, CPoolPair pool) { - - if (!pool.swapEvent) { - return true; // no events, skip to the next pool - } - - CAmount distributedFeeA = 0; - CAmount distributedFeeB = 0; - - ForEachPoolShare([&] (DCT_ID const & currentId, CScript const & provider, uint32_t height) { - if (currentId != poolId) { - return false; // stop - } - CAmount const liquidity = onGetBalance(provider, poolId).nValue; - - // distribute trading fees - if (newRewardCalc) { - distributedFeeA += static_cast((arith_uint256(pool.blockCommissionA) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); - distributedFeeB += static_cast((arith_uint256(pool.blockCommissionB) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); - } else { - uint32_t const liqWeight = liquidity * PRECISION / pool.totalLiquidity; - assert (liqWeight < PRECISION); - distributedFeeA += pool.blockCommissionA * liqWeight / PRECISION; - distributedFeeB += pool.blockCommissionB * liqWeight / PRECISION; - } - return true; - }, PoolShareKey{poolId, CScript{}}); - - pool.blockCommissionA -= distributedFeeA; - pool.blockCommissionB -= distributedFeeB; - pool.swapEvent = false; - - auto res = SetPoolPair(poolId, UINT_MAX, pool); - if (!res.ok) { - LogPrintf("Pool rewards: can't update pool (id=%s) state: %s\n", poolId.ToString(), res.msg); - } - return true; - }); - } + CAmount UpdatePoolRewards(std::function onGetBalance, std::function onTransfer, int nHeight = 0); // tags struct ByID { static const unsigned char prefix; }; // lsTokenID -> СPoolPair diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index 610c5ae245f..2f5b1706a5c 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -106,7 +106,7 @@ static void onPoolRewards(CCustomCSView & view, CScript const & owner, uint32_t }; auto beginHeight = std::max(*height, begin); view.CalculatePoolRewards(poolId, onLiquidity, beginHeight, end, - [&](CScript const &, uint8_t type, CTokenAmount amount, uint32_t height) { + [&](uint8_t type, CTokenAmount amount, uint32_t height) { onReward(height, poolId, type, amount); mnview.AddBalance(owner, amount); // update owner liquidity } diff --git a/src/test/liquidity_tests.cpp b/src/test/liquidity_tests.cpp index d49c1299748..4b723b4c69c 100644 --- a/src/test/liquidity_tests.cpp +++ b/src/test/liquidity_tests.cpp @@ -412,7 +412,7 @@ BOOST_AUTO_TEST_CASE(owner_rewards) return mnview.GetBalance(shareAddress[0], idPool).nValue; }; mnview.CalculatePoolRewards(idPool, onLiquidity, 1, 10, - [&](CScript const &, uint8_t type, CTokenAmount amount, uint32_t height) { + [&](uint8_t type, CTokenAmount amount, uint32_t height) { switch(type) { case int(RewardType::Rewards): BOOST_CHECK_EQUAL(amount.nValue, oldRewardCalculation(onLiquidity(), pool)); @@ -447,7 +447,7 @@ BOOST_AUTO_TEST_CASE(owner_rewards) mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) { pool.swapEvent = true; pool.ownerAddress = shareAddress[1]; - pool.rewards = CBalances{TAmounts{{idPool, COIN}}}; + pool.rewards = CBalances{TAmounts{{DCT_ID{idPool.v+1}, COIN}}}; mnview.SetPoolPair(idPool, 8, pool); return false; }); @@ -457,10 +457,9 @@ BOOST_AUTO_TEST_CASE(owner_rewards) return mnview.GetBalance(shareAddress[1], idPool).nValue; }; mnview.CalculatePoolRewards(idPool, onLiquidity, 1, 10, - [&](CScript const & from, uint8_t type, CTokenAmount amount, uint32_t height) { + [&](uint8_t type, CTokenAmount amount, uint32_t height) { if (height >= Params().GetConsensus().BayfrontGardensHeight) { - if (!from.empty()) { - BOOST_CHECK(from == shareAddress[1]); + if (amount.nTokenId == DCT_ID{idPool.v+1}) { for (const auto& reward : pool.rewards.balances) { auto providerReward = static_cast((arith_uint256(reward.second) * arith_uint256(onLiquidity()) / arith_uint256(pool.totalLiquidity)).GetLow64()); BOOST_CHECK_EQUAL(amount.nValue, providerReward); diff --git a/src/validation.cpp b/src/validation.cpp index ca5d9d46a15..57bcc5cf290 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2360,10 +2360,26 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl CCustomCSView cache(mnview); // hardfork commissions update - cache.UpdatePoolCommissions([&](CScript const & owner, DCT_ID tokenID) { - cache.CalculateOwnerRewards(owner, pindex->nHeight); - return cache.GetBalance(owner, tokenID); - }, pindex->nHeight); + CAmount distributed = cache.UpdatePoolRewards( + [&](CScript const & owner, DCT_ID tokenID) { + cache.CalculateOwnerRewards(owner, pindex->nHeight); + return cache.GetBalance(owner, tokenID); + }, + [&](CScript const & owner, CTokenAmount amount) { + auto res = cache.SubBalance(owner, amount); + if (!res) { + LogPrintf("Custom pool rewards: can't subtract balance of %s: %s, height %ld\n", owner.GetHex(), res.msg, pindex->nHeight); + } + return res; + }, + pindex->nHeight + ); + + auto res = cache.SubCommunityBalance(CommunityAccountType::IncentiveFunding, distributed); + if (!res.ok) { + LogPrintf("Pool rewards: can't update community balance: %s. Block %ld (%s)\n", res.msg, block.height, block.GetHash().ToString()); + } + // Remove `Finalized` and/or `LPS` flags _possibly_set_ by bytecoded (cheated) txs before bayfront fork if (pindex->nHeight == chainparams.GetConsensus().BayfrontHeight - 1) { // call at block _before_ fork cache.BayfrontFlagsCleanup();