From 4feeddfd2c36d9214edffeee65cecf5771e40364 Mon Sep 17 00:00:00 2001 From: Peter John Bushnell Date: Mon, 17 May 2021 11:36:46 +0100 Subject: [PATCH] Change daily LP rewards automatically on subsidy reduction (#390) --- src/init.cpp | 1 + .../govvariables/lp_daily_dfi_reward.cpp | 6 ++- src/validation.cpp | 39 ++++++++++++++++-- test/functional/feature_burn_address.py | 8 ++-- .../feature_dfip8_communitybalances.py | 32 ++++++++++++++- test/functional/feature_poolswap_mechanism.py | 28 +++++++++---- test/functional/feature_setgov.py | 40 +++++++++++++++---- 7 files changed, 127 insertions(+), 27 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index b62b7e3c94..b71933530c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -459,6 +459,7 @@ void SetupServerArgs() gArgs.AddArg("-masternode_operator=
", "Masternode operator address (default: empty)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-dummypos", "Flag to skip PoS-related checks (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-txnotokens", "Flag to force old tx serialization (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-subsidytest", "Flag to enable new subsidy rules (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-anchorquorum", "Min quorum size (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-spv", "Enable SPV to bitcoin blockchain (default: 1)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-criminals", "punishment of criminal nodes (default: 0, regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); diff --git a/src/masternodes/govvariables/lp_daily_dfi_reward.cpp b/src/masternodes/govvariables/lp_daily_dfi_reward.cpp index cc382ad6fa..2a3c603439 100644 --- a/src/masternodes/govvariables/lp_daily_dfi_reward.cpp +++ b/src/masternodes/govvariables/lp_daily_dfi_reward.cpp @@ -18,8 +18,12 @@ UniValue LP_DAILY_DFI_REWARD::Export() const { return ValueFromAmount(dailyReward); } -Res LP_DAILY_DFI_REWARD::Validate(const CCustomCSView &) const +Res LP_DAILY_DFI_REWARD::Validate(const CCustomCSView & view) const { + if (view.GetLastHeight() >= static_cast(Params().GetConsensus().EunosHeight)) { + return Res::Err("Cannot be set manually after Eunos hard fork"); + } + // nothing to do return Res::Ok(); } diff --git a/src/validation.cpp b/src/validation.cpp index 3ab206f861..64ddf28c07 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1131,14 +1131,15 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) { CAmount nSubsidy = consensusParams.baseBlockSubsidy; - if (Params().NetworkIDString() != CBaseChainParams::REGTEST || consensusParams.EunosHeight == 1) + if (Params().NetworkIDString() != CBaseChainParams::REGTEST || + (Params().NetworkIDString() == CBaseChainParams::REGTEST && gArgs.GetBoolArg("-subsidytest", false))) { if (nHeight >= consensusParams.EunosHeight) { nSubsidy = consensusParams.newBaseBlockSubsidy; const size_t reductions = (nHeight - consensusParams.EunosHeight) / consensusParams.emissionReductionPeriod; - // See if we already have thie reduction calculated and return if found. + // See if we already have this reduction calculated and return if found. if (subsidyReductions.find(reductions) != subsidyReductions.end()) { return subsidyReductions[reductions]; @@ -2085,7 +2086,18 @@ void ReverseGeneralCoinbaseTx(CCustomCSView & mnview, int height) for (const auto& kv : Params().GetConsensus().newNonUTXOSubsidies) { CAmount subsidy = CalculateCoinbaseReward(blockReward, kv.second); - mnview.SubCommunityBalance(kv.first, subsidy); + + // Remove Swap, Futures and Options balances from Unallocated + if (kv.first == CommunityAccountType::Swap || + kv.first == CommunityAccountType::Futures || + kv.first == CommunityAccountType::Options) + { + mnview.SubCommunityBalance(CommunityAccountType::Unallocated, subsidy); + } + else + { + mnview.SubCommunityBalance(kv.first, subsidy); + } } } else @@ -2571,6 +2583,27 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // make all changes to the new cache/snapshot to make it possible to take a diff later: CCustomCSView cache(mnview); + // Hard coded LP_DAILY_DFI_REWARD change + if (pindex->nHeight >= chainparams.GetConsensus().EunosHeight) + { + const auto& incentivePair = chainparams.GetConsensus().newNonUTXOSubsidies.find(CommunityAccountType::IncentiveFunding); + if (incentivePair != chainparams.GetConsensus().newNonUTXOSubsidies.end()) + { + CAmount subsidy = CalculateCoinbaseReward(GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus()), incentivePair->second); + // Change daily LP reward if it has changed + auto var = cache.GetVariable(LP_DAILY_DFI_REWARD::TypeName()); + if (var) { + // Cast to avoid UniValue in GovVariable Export/Import + auto lpVar = dynamic_cast(var.get()); + if (lpVar && lpVar->dailyReward != subsidy) { + lpVar->dailyReward = subsidy; + lpVar->Apply(cache, pindex->nHeight); + cache.SetVariable(*lpVar); + } + } + } + } + // hardfork commissions update CAmount distributed = cache.UpdatePoolRewards( [&](CScript const & owner, DCT_ID tokenID) { diff --git a/test/functional/feature_burn_address.py b/test/functional/feature_burn_address.py index a8f04064e2..65f2164e93 100755 --- a/test/functional/feature_burn_address.py +++ b/test/functional/feature_burn_address.py @@ -106,10 +106,8 @@ def run_test(self): assert_equal(result[0]['type'], 'AccountToAccount') assert_equal(result[0]['amounts'][0], '1.00000000@DFI') - # Auto auth from failed accounttoaccount - assert_equal(result[1]['owner'], 'mfburnZSAM7Gs1hpDeNaMotJXSGA7edosG') - assert_equal(result[1]['type'], 'None') - assert_equal(result[1]['amounts'][0], '0.99988640@DFI') + # Auto auth burnt amount + auth_burn_amount = result[1]['amounts'][0][0:10] # Send to burn address with accounttoutxos self.nodes[0].accounttoutxos(funded_address, {burn_address:"2@0"}) @@ -150,7 +148,7 @@ def run_test(self): # Test output of getburninfo result = self.nodes[0].getburninfo() assert_equal(result['address'], burn_address) - assert_equal(result['amount'], Decimal('12.99988640')) + assert_equal(result['amount'], Decimal('12.00000000') + Decimal(auth_burn_amount)) assert_equal(result['tokens'][0], '1.00000000@DFI') assert_equal(result['tokens'][1], '100.00000000@GOLD#128') assert_equal(result['feeburn'], Decimal('2.00000000')) diff --git a/test/functional/feature_dfip8_communitybalances.py b/test/functional/feature_dfip8_communitybalances.py index 51518dc0af..d6dc703dca 100644 --- a/test/functional/feature_dfip8_communitybalances.py +++ b/test/functional/feature_dfip8_communitybalances.py @@ -14,10 +14,9 @@ class Dfip8Test(DefiTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True - self.extra_args = [['-txnotokens=0', '-amkheight=1', '-eunosheight=1']] + self.extra_args = [['-txnotokens=0', '-amkheight=1', '-eunosheight=1', '-subsidytest=1']] def run_test(self): - self.nodes[0].generate(1) result = self.nodes[0].listcommunitybalances() @@ -76,6 +75,35 @@ def run_test(self): assert_equal(getblock['nonutxo'][0]['IncentiveFunding'], Decimal('101.37356916')) assert_equal(getblock['nonutxo'][0]['Burnt'], Decimal('144.55193810')) + # Invalidate a block and test rollback + self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) + result = self.nodes[0].listcommunitybalances() + + assert_equal(result['AnchorReward'], Decimal('12.23086488')) + assert_equal(result['IncentiveFunding'], Decimal('15563.77556916')) + assert_equal(result['Burnt'], Decimal('22192.90433810')) + + getblock = self.nodes[0].getblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) + + assert_equal(getblock['nonutxo'][0]['AnchorReward'], Decimal('0.07966488')) + assert_equal(getblock['nonutxo'][0]['IncentiveFunding'], Decimal('101.37356916')) + assert_equal(getblock['nonutxo'][0]['Burnt'], Decimal('144.55193810')) + + #Go forward again to first reduction + self.nodes[0].generate(1) + + result = self.nodes[0].listcommunitybalances() + + assert_equal(result['AnchorReward'], Decimal('12.31052976')) + assert_equal(result['IncentiveFunding'], Decimal('15665.14913832')) + assert_equal(result['Burnt'], Decimal('22337.45627620')) + + getblock = self.nodes[0].getblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) + + assert_equal(getblock['nonutxo'][0]['AnchorReward'], Decimal('0.07966488')) + assert_equal(getblock['nonutxo'][0]['IncentiveFunding'], Decimal('101.37356916')) + assert_equal(getblock['nonutxo'][0]['Burnt'], Decimal('144.55193810')) + # Ten reductions plus one self.nodes[0].generate(1502 - self.nodes[0].getblockcount()) diff --git a/test/functional/feature_poolswap_mechanism.py b/test/functional/feature_poolswap_mechanism.py index d6158e0dd2..937ff26dcc 100755 --- a/test/functional/feature_poolswap_mechanism.py +++ b/test/functional/feature_poolswap_mechanism.py @@ -25,9 +25,9 @@ def set_test_params(self): # node2: revert create (all) self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=0', '-bayfrontheight=0', '-bayfrontgardensheight=0', '-eunosheight=120'], - ['-txnotokens=0', '-amkheight=0', '-bayfrontheight=0', '-bayfrontgardensheight=0', '-eunosheight=120'], - ['-txnotokens=0', '-amkheight=0', '-bayfrontheight=0', '-bayfrontgardensheight=0', '-eunosheight=120'] + ['-txnotokens=0', '-amkheight=0', '-bayfrontheight=0', '-bayfrontgardensheight=0', '-eunosheight=120', '-subsidytest=1'], + ['-txnotokens=0', '-amkheight=0', '-bayfrontheight=0', '-bayfrontgardensheight=0', '-eunosheight=120', '-subsidytest=1'], + ['-txnotokens=0', '-amkheight=0', '-bayfrontheight=0', '-bayfrontgardensheight=0', '-eunosheight=120', '-subsidytest=1'] ] # SET parameters for create tokens and pools @@ -71,6 +71,7 @@ def create_token(self, symbol, address): "collateralAddress": address }, []) self.nodes[0].generate(1) + self.sync_blocks() self.tokens.append(symbol) def create_pool(self, tokenA, tokenB, owner): @@ -82,6 +83,7 @@ def create_pool(self, tokenA, tokenB, owner): "ownerAddress": owner }, [])) self.nodes[0].generate(1) + self.sync_blocks() def create_pools(self, owner): for i in range(self.COUNT_POOLS): @@ -100,6 +102,7 @@ def mint_tokens(self, owner): self.nodes[0].generate(1) self.nodes[0].minttokens(mint_amount + "@" + self.get_id_token(item), []) self.nodes[0].generate(1) + self.sync_blocks() return mint_amount def send_tokens(self, owner): @@ -117,6 +120,7 @@ def send_tokens(self, owner): self.nodes[0].generate(1) self.nodes[0].accounttoaccount(owner, outputs, []) self.nodes[0].generate(1) + self.sync_blocks() def add_pools_liquidity(self, owner): for item in range(self.COUNT_POOLS): @@ -143,6 +147,7 @@ def add_pools_liquidity(self, owner): self.accounts[idx]: [amountA, amountB] }, self.accounts[idx], []) self.nodes[0].generate(1) + self.sync_blocks() def slope_swap(self, unswapped, poolFrom, poolTo): swapped = poolTo - (poolTo * poolFrom / (poolFrom + unswapped)) @@ -151,7 +156,7 @@ def slope_swap(self, unswapped, poolFrom, poolTo): return (poolFrom, poolTo) - def poolswap(self): + def poolswap(self, nodes): for item in range(self.COUNT_POOLS): tokenA = "GOLD" + str(item) tokenB = "SILVER" + str(item) @@ -165,6 +170,7 @@ def poolswap(self): for idx in range(start, end): self.nodes[0].sendmany("", { self.accounts[idx] : 0.02 }) self.nodes[0].generate(1) + self.sync_blocks(nodes) amount = random.randint(1, self.AMOUNT_TOKEN // 2) amountsB = {} @@ -187,6 +193,7 @@ def poolswap(self): "tokenTo": str(self.get_id_token(tokenA)), }, []) self.nodes[0].generate(1) + self.sync_blocks(nodes) for idx in range(start, end): liquidity = self.nodes[0].getaccount(self.accounts[idx], {}, True)[idPool] @@ -222,10 +229,9 @@ def run_test(self): print("Generating initial chain...") self.nodes[0].generate(100) - self.sync_all() - owner = self.nodes[0].getnewaddress("", "legacy") self.nodes[0].generate(1) + self.sync_blocks() # START #======================== @@ -280,6 +286,7 @@ def run_test(self): self.nodes[0].setgov({ "LP_SPLITS": obj }) self.nodes[0].setgov({ "LP_DAILY_DFI_REWARD": self.LP_DAILY_DFI_REWARD }) self.nodes[0].generate(1) + self.sync_blocks() g1 = self.nodes[0].getgov("LP_SPLITS") for i in range(self.COUNT_POOLS): @@ -294,7 +301,8 @@ def run_test(self): print("Swapping tokens...") start_time = time.time() - self.poolswap() + nodes = self.nodes[0:2] + self.poolswap(nodes) end_time = time.time() - start_time print("Tokens exchanged") print("Elapsed time: {} s".format(end_time)) @@ -309,8 +317,12 @@ def run_test(self): self.sync_blocks() assert(self.nodes[0].getblockcount() == 120) # eunos + + self.LP_DAILY_DFI_REWARD = self.nodes[0].getgov("LP_DAILY_DFI_REWARD")['LP_DAILY_DFI_REWARD'] + assert_equal(self.LP_DAILY_DFI_REWARD, Decimal('103.08268000')) + print("Swapping tokens after eunos height...") - self.poolswap() + self.poolswap(self.nodes) if __name__ == '__main__': diff --git a/test/functional/feature_setgov.py b/test/functional/feature_setgov.py index f85334ff3f..0345ea263a 100755 --- a/test/functional/feature_setgov.py +++ b/test/functional/feature_setgov.py @@ -12,7 +12,7 @@ from test_framework.authproxy import JSONRPCException from test_framework.util import \ - connect_nodes, disconnect_nodes + connect_nodes, disconnect_nodes, assert_equal from decimal import Decimal @@ -21,16 +21,11 @@ def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50'], - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50']] + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-eunosheight=200', '-subsidytest=1'], + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-eunosheight=200', '-subsidytest=1']] def run_test(self): - # fast check for debug - print (self.nodes[0].getgov("LP_SPLITS")) - print (self.nodes[0].getgov("LP_DAILY_DFI_REWARD")) - # return - print("Generating initial chain...") self.setup_tokens() @@ -200,6 +195,35 @@ def run_test(self): and pool2['rewardPct'] == Decimal('0.40000000') and pool3['rewardPct'] == Decimal('0.10000000')) + # Generate to Eunos hard fork + self.nodes[0].clearmempool() + self.nodes[0].generate(200 - self.nodes[0].getblockcount()) + + # Try and set LP_DAILY_DFI_REWARD manually + try: + self.nodes[0].setgov({ "LP_DAILY_DFI_REWARD": 100}) + except JSONRPCException as e: + errorString = e.error['message'] + assert("Cannot be set manually after Eunos hard fork" in errorString) + + # Check new subsidy + assert_equal(self.nodes[0].getgov('LP_DAILY_DFI_REWARD')['LP_DAILY_DFI_REWARD'], Decimal('103.08268000')) + + # Roll back + self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) + + # Check subsidy restored + assert_equal(self.nodes[0].getgov('LP_DAILY_DFI_REWARD')['LP_DAILY_DFI_REWARD'], Decimal('35.50000000')) + + # Move to second reduction and check reward + self.nodes[0].generate(151) + assert_equal(self.nodes[0].getgov('LP_DAILY_DFI_REWARD')['LP_DAILY_DFI_REWARD'], Decimal('101.37356916')) + + # Rollback from second reduction + self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) + + # Check subsidy restored + assert_equal(self.nodes[0].getgov('LP_DAILY_DFI_REWARD')['LP_DAILY_DFI_REWARD'], Decimal('103.08268000')) if __name__ == '__main__': GovsetTest ().main ()