Skip to content

Commit

Permalink
Update rewards on every block as drop-in replacement
Browse files Browse the repository at this point in the history
Signed-off-by: Anthony Fieroni <[email protected]>
  • Loading branch information
bvbfan committed Mar 29, 2021
1 parent 96e675e commit 794e093
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 123 deletions.
15 changes: 1 addition & 14 deletions src/masternodes/masternodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
247 changes: 194 additions & 53 deletions src/masternodes/poolpairs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename Stream, typename Operation>
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 <typename T>
inline bool IterReadKey(std::unique_ptr<CStorageKVIterator> & 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) {
Expand All @@ -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<ByDailyReward>(PoolHeightKey{{}, height});
auto itPct = LowerBound(dbPct);
if (!IterReadKey(itPct, dbPct, DCT_ID{})) {
return 0;
}
return ReadValue<CAmount>(itPct->Value());
};

// create new
if (!poolPairByID && !poolPairByTokens) {
WriteBy<ByID>(poolId, pool);
WriteBy<ByPair>(ByPairKey{pool.idTokenA, pool.idTokenB}, poolId);
WriteBy<ByPair>(ByPairKey{pool.idTokenB, pool.idTokenA}, poolId);
if (height < UINT_MAX) {
WriteBy<ByPoolHeight>(PoolHeightKey{poolId, height}, pool);
WriteBy<ByPoolHeight>(PoolHeightKey{poolId, height}, PoolHeightValue(pool, dailyReward()));
}
return Res::Ok();
}
Expand All @@ -43,7 +99,7 @@ Res CPoolPairView::SetPoolPair(DCT_ID const & poolId, uint32_t height, CPoolPair
&& poolPairByTokens->second.idTokenB == pool.idTokenB) {
WriteBy<ByID>(poolId, pool);
if (height < UINT_MAX) {
WriteBy<ByPoolHeight>(PoolHeightKey{poolId, height}, pool);
WriteBy<ByPoolHeight>(PoolHeightKey{poolId, height}, PoolHeightValue(pool, dailyReward()));
}
return Res::Ok();
}
Expand Down Expand Up @@ -122,71 +178,38 @@ boost::optional<std::pair<DCT_ID, CPoolPair> > CPoolPairView::GetPoolPair(const
return {};
}

template <typename T>
inline bool IterReadKey(std::unique_ptr<CStorageKVIterator> & 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<CAmount>((arith_uint256(reward) * arith_uint256(liquidity) / arith_uint256(totalLiquidity)).GetLow64());
}

template <typename T>
inline uint32_t IterReadKeyHeight(std::unique_ptr<CStorageKVIterator> & 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<CAmount()> onLiquidity, uint32_t begin, uint32_t end, std::function<void(CScript const &, uint8_t, CTokenAmount, uint32_t)> onReward) {
void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function<CAmount()> onLiquidity, uint32_t begin, uint32_t end, std::function<void(uint8_t, CTokenAmount, uint32_t)> 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<ByDailyReward>(PoolHeightKey{{}, begin});
auto dbPool = MakeDbKey<ByPoolHeight>(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<CAmount>(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<CPoolPair>(itPool->Value());
auto poolReward = poolRewardPerBlock(dailyReward, pool.rewardPct);
const auto pool = ReadValue<PoolHeightValue>(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<CAmount>(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<CAmount>((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) {
Expand All @@ -196,16 +219,16 @@ void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function<CA
feeA = pool.blockCommissionA * liqWeight / PRECISION;
feeB = pool.blockCommissionB * liqWeight / PRECISION;
} else {
feeA = static_cast<CAmount>((arith_uint256(pool.blockCommissionA) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64());
feeB = static_cast<CAmount>((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<CAmount>((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);
}
}
}
Expand Down Expand Up @@ -366,6 +389,118 @@ CAmount CPoolPair::slopeSwap(CAmount unswapped, CAmount &poolFrom, CAmount &pool
return swapped.GetLow64();
}

CAmount CPoolPairView::UpdatePoolRewards(std::function<CTokenAmount(CScript const &, DCT_ID)> onGetBalance, std::function<Res(CScript const &, CTokenAmount)> 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<ByPoolHeight>(PoolHeightKey{poolId, height});
auto itPool = LowerBound(dbPool);
if (IterReadKey(itPool, dbPool, poolId)) {
poolHeight = ReadValue<PoolHeightValue>(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<ByPoolHeight>(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<ByPoolHeight>(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<ByShare>(PoolShareKey{poolId, provider}, height);
return Res::Ok();
Expand All @@ -381,6 +516,12 @@ boost::optional<uint32_t> 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<ByPoolHeight>(PoolHeightKey{id, height}, PoolHeightValue(pool, reward));
}
return true;
});
WriteBy<ByDailyReward>(PoolHeightKey{{}, height}, reward);
return Res::Ok();
}
Expand Down
Loading

0 comments on commit 794e093

Please sign in to comment.