From a5508635b50ee0fbe1ecfb333a4fa2839939bf31 Mon Sep 17 00:00:00 2001 From: Mihailo Milenkovic Date: Thu, 23 Jun 2022 13:42:56 +0200 Subject: [PATCH] Burn loan interest in DUSD instead of swapping to DFI (#1357) * Init dev * Track all tokens in interest burn * Improve tests * Rename DFIP2206 to DFIP2206A. Rename SwapToDFIoverUSD function. Co-authored-by: Peter Bushnell --- src/masternodes/govvariables/attributes.cpp | 63 +++--- src/masternodes/govvariables/attributes.h | 15 +- src/masternodes/mn_checks.cpp | 42 +++- src/masternodes/mn_checks.h | 2 +- src/masternodes/rpc_accounts.cpp | 8 +- src/validation.cpp | 22 +- test/functional/feature_loan_basics.py | 4 +- test/functional/feature_loan_payback_dfi.py | 202 +++++++++++++++++- .../functional/feature_loan_payback_dfi_v2.py | 30 +-- test/functional/feature_setgov.py | 12 +- 10 files changed, 329 insertions(+), 71 deletions(-) diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index daf00cd2129..f07de4961a1 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -77,6 +77,7 @@ const std::map& ATTRIBUTES::allowedParamIDs() { static const std::map params{ {"dfip2201", ParamIDs::DFIP2201}, {"dfip2203", ParamIDs::DFIP2203}, + {"dfip2206a", ParamIDs::DFIP2206A}, }; return params; } @@ -92,6 +93,7 @@ const std::map& ATTRIBUTES::displayParamsIDs() { static const std::map params{ {ParamIDs::DFIP2201, "dfip2201"}, {ParamIDs::DFIP2203, "dfip2203"}, + {ParamIDs::DFIP2206A, "dfip2206a"}, {ParamIDs::TokenID, "token"}, {ParamIDs::Economy, "economy"}, }; @@ -140,11 +142,13 @@ const std::map>& ATTRIBUTES::allowedKeys }, { AttributeTypes::Param, { - {"active", DFIPKeys::Active}, - {"minswap", DFIPKeys::MinSwap}, - {"premium", DFIPKeys::Premium}, - {"reward_pct", DFIPKeys::RewardPct}, - {"block_period", DFIPKeys::BlockPeriod}, + {"active", DFIPKeys::Active}, + {"minswap", DFIPKeys::MinSwap}, + {"premium", DFIPKeys::Premium}, + {"reward_pct", DFIPKeys::RewardPct}, + {"block_period", DFIPKeys::BlockPeriod}, + {"direct_interest_dusd_burn", DFIPKeys::DirectInterestDUSDBurn}, + {"direct_loan_dusd_burn", DFIPKeys::DirectLoanDUSDBurn}, } }, }; @@ -182,11 +186,13 @@ const std::map>& ATTRIBUTES::displayKeys }, { AttributeTypes::Param, { - {DFIPKeys::Active, "active"}, - {DFIPKeys::Premium, "premium"}, - {DFIPKeys::MinSwap, "minswap"}, - {DFIPKeys::RewardPct, "reward_pct"}, - {DFIPKeys::BlockPeriod, "block_period"}, + {DFIPKeys::Active, "active"}, + {DFIPKeys::Premium, "premium"}, + {DFIPKeys::MinSwap, "minswap"}, + {DFIPKeys::RewardPct, "reward_pct"}, + {DFIPKeys::BlockPeriod, "block_period"}, + {DFIPKeys::DirectInterestDUSDBurn, "direct_interest_dusd_burn"}, + {DFIPKeys::DirectLoanDUSDBurn, "direct_loan_dusd_burn"}, } }, { @@ -338,11 +344,13 @@ const std::mapsecond; } else if (type == AttributeTypes::Locks) { @@ -469,20 +477,25 @@ Res ATTRIBUTES::ProcessVariable(const std::string& key, const std::string& value if (type == AttributeTypes::Param) { if (typeId == ParamIDs::DFIP2201) { - if (typeKey == DFIPKeys::RewardPct || - typeKey == DFIPKeys::BlockPeriod) { + if (typeKey != DFIPKeys::Active && typeKey != DFIPKeys::Premium && + typeKey != DFIPKeys::MinSwap ) { return Res::Err("Unsupported type for DFIP2201 {%d}", typeKey); } } else if (typeId == ParamIDs::DFIP2203) { - if (typeKey == DFIPKeys::Premium || - typeKey == DFIPKeys::MinSwap) { + if (typeKey != DFIPKeys::Active && typeKey != DFIPKeys::RewardPct && + typeKey != DFIPKeys::BlockPeriod) { return Res::Err("Unsupported type for DFIP2203 {%d}", typeKey); } if (typeKey == DFIPKeys::BlockPeriod) { futureBlockUpdated = true; } - } else { + } else if (typeId == ParamIDs::DFIP2206A) { + if (typeKey != DFIPKeys::DirectInterestDUSDBurn && + typeKey != DFIPKeys::DirectLoanDUSDBurn) { + return Res::Err("Unsupported type for DFIP2206A {%d}", typeKey); + } + } else { return Res::Err("Unsupported Param ID"); } } @@ -889,10 +902,12 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const break; case AttributeTypes::Param: - if (attrV0->typeId == ParamIDs::DFIP2203) { - if (view.GetLastHeight() < Params().GetConsensus().FortCanningRoadHeight) { + if (attrV0->typeId == ParamIDs::DFIP2206A) { + if (view.GetLastHeight() < Params().GetConsensus().FortCanningGardensHeight) + return Res::Err("Cannot be set before FortCanningGarden"); + } else if (attrV0->typeId == ParamIDs::DFIP2203) { + if (view.GetLastHeight() < Params().GetConsensus().FortCanningRoadHeight) return Res::Err("Cannot be set before FortCanningRoad"); - } } else if (attrV0->typeId != ParamIDs::DFIP2201) { return Res::Err("Unrecognised param id"); } diff --git a/src/masternodes/govvariables/attributes.h b/src/masternodes/govvariables/attributes.h index 835e74af440..6e00169dee9 100644 --- a/src/masternodes/govvariables/attributes.h +++ b/src/masternodes/govvariables/attributes.h @@ -28,6 +28,7 @@ enum ParamIDs : uint8_t { DFIP2203 = 'b', TokenID = 'c', Economy = 'e', + DFIP2206A = 'f', }; enum OracleIDs : uint8_t { @@ -43,11 +44,13 @@ enum EconomyKeys : uint8_t { }; enum DFIPKeys : uint8_t { - Active = 'a', - Premium = 'b', - MinSwap = 'c', - RewardPct = 'd', - BlockPeriod = 'e', + Active = 'a', + Premium = 'b', + MinSwap = 'c', + RewardPct = 'd', + BlockPeriod = 'e', + DirectInterestDUSDBurn = 'g', + DirectLoanDUSDBurn = 'h', }; enum TokenKeys : uint8_t { @@ -175,7 +178,7 @@ class ATTRIBUTES : public GovVariable, public AutoRegistratorsymbol, subInterest, height); - res = SwapToDFIOverUSD(mnview, loanTokenId, subInterest, obj.from, consensus.burnAddress, height); + res = SwapToDFIorDUSD(mnview, loanTokenId, subInterest, obj.from, consensus.burnAddress, height); } } else @@ -3375,7 +3375,10 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor LogPrint(BCLog::LOAN, "CLoanPaybackLoanMessage(): Swapping %s to DFI and burning it - total loan %lld (%lld %s), height - %d\n", paybackToken->symbol, subLoan + subInterest, subInToken, paybackToken->symbol, height); - res = SwapToDFIOverUSD(mnview, paybackTokenId, subInToken, obj.from, consensus.burnAddress, height); + CDataStructureV0 directBurnKey{AttributeTypes::Param, ParamIDs::DFIP2206A, DFIPKeys::DirectLoanDUSDBurn}; + auto directLoanBurn = attributes->GetValue(directBurnKey, false); + + res = SwapToDFIorDUSD(mnview, paybackTokenId, subInToken, obj.from, consensus.burnAddress, height, !directLoanBurn); } } @@ -4078,7 +4081,7 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo return poolResult; } -Res SwapToDFIOverUSD(CCustomCSView & mnview, DCT_ID tokenId, CAmount amount, CScript const & from, CScript const & to, uint32_t height) +Res SwapToDFIorDUSD(CCustomCSView & mnview, DCT_ID tokenId, CAmount amount, CScript const & from, CScript const & to, uint32_t height, bool forceLoanSwap) { CPoolSwapMessage obj; @@ -4099,9 +4102,28 @@ Res SwapToDFIOverUSD(CCustomCSView & mnview, DCT_ID tokenId, CAmount amount, CS if (!dUsdToken) return Res::Err("Cannot find token DUSD"); + const auto attributes = mnview.GetAttributes(); + if (!attributes) { + return Res::Err("Attributes unavailable"); + } + CDataStructureV0 directBurnKey{AttributeTypes::Param, ParamIDs::DFIP2206A, DFIPKeys::DirectInterestDUSDBurn}; + // Direct swap from DUSD to DFI as defined in the CPoolSwapMessage. if (tokenId == dUsdToken->first) { - return poolSwap.ExecuteSwap(mnview, {}); + if (to == Params().GetConsensus().burnAddress && !forceLoanSwap && attributes->GetValue(directBurnKey, false)) + { + // direct burn dUSD + CTokenAmount dUSD{dUsdToken->first, amount}; + + auto res = mnview.SubBalance(from, dUSD); + if (!res) + return res; + + return mnview.AddBalance(to, dUSD); + } + else + // swap dUSD -> DFI and burn DFI + return poolSwap.ExecuteSwap(mnview, {}); } auto pooldUSDDFI = mnview.GetPoolPair(dUsdToken->first, DCT_ID{0}); @@ -4112,10 +4134,16 @@ Res SwapToDFIOverUSD(CCustomCSView & mnview, DCT_ID tokenId, CAmount amount, CS if (!poolTokendUSD) return Res::Err("Cannot find pool pair %s-DUSD!", token->symbol); - // swap tokenID -> USD -> DFI - auto res = poolSwap.ExecuteSwap(mnview, {poolTokendUSD->first, pooldUSDDFI->first}); + if (to == Params().GetConsensus().burnAddress && !forceLoanSwap && attributes->GetValue(directBurnKey, false)) + { + obj.idTokenTo = dUsdToken->first; - return res; + // swap tokenID -> dUSD and burn dUSD + return poolSwap.ExecuteSwap(mnview, {}); + } + else + // swap tokenID -> dUSD -> DFI and burn DFI + return poolSwap.ExecuteSwap(mnview, {poolTokendUSD->first, pooldUSDDFI->first}); } bool IsVaultPriceValid(CCustomCSView& mnview, const CVaultId& vaultId, uint32_t height) diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h index 26e4cdb0565..d761a0c2f03 100644 --- a/src/masternodes/mn_checks.h +++ b/src/masternodes/mn_checks.h @@ -393,7 +393,7 @@ ResVal ApplyAnchorRewardTx(CCustomCSView& mnview, const CTransaction& t ResVal ApplyAnchorRewardTxPlus(CCustomCSView& mnview, const CTransaction& tx, int height, const std::vector& metadata, const Consensus::Params& consensusParams); ResVal GetAggregatePrice(CCustomCSView& view, const std::string& token, const std::string& currency, uint64_t lastBlockTime); bool IsVaultPriceValid(CCustomCSView& mnview, const CVaultId& vaultId, uint32_t height); -Res SwapToDFIOverUSD(CCustomCSView & mnview, DCT_ID tokenId, CAmount amount, CScript const & from, CScript const & to, uint32_t height); +Res SwapToDFIorDUSD(CCustomCSView & mnview, DCT_ID tokenId, CAmount amount, CScript const & from, CScript const & to, uint32_t height, bool forceLoanSwap = false); Res storeGovVars(const CGovernanceHeightMessage& obj, CCustomCSView& view); inline bool OraclePriceFeed(CCustomCSView& view, const CTokenCurrencyPair& priceFeed) { diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index 1c804716304..4520a4f942d 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -1771,13 +1771,13 @@ UniValue getburninfo(const JSONRPCRequest& request) { CAmount burntDFI{0}; CAmount burntFee{0}; CAmount auctionFee{0}; - CAmount paybackFee{0}; CAmount dfiPaybackFee{0}; CAmount burnt{0}; CBalances burntTokens; CBalances dexfeeburn; CBalances paybackfees; + CBalances paybackFee; CBalances paybacktokens; CBalances dfi2203Tokens; CBalances dfipaybacktokens; @@ -1838,8 +1838,8 @@ UniValue getburninfo(const JSONRPCRequest& request) { // withdraw burn if (value.category == uint8_t(CustomTxType::PaybackLoan) || value.category == uint8_t(CustomTxType::PaybackLoanV2)) { - for (auto const & diff : value.diff) { - paybackFee += diff.second; + for (const auto& [id, amount] : value.diff) { + paybackFee.Add({id, amount}); } return true; } @@ -1874,7 +1874,7 @@ UniValue getburninfo(const JSONRPCRequest& request) { result.pushKV("tokens", AmountsToJSON(burntTokens.balances)); result.pushKV("feeburn", ValueFromAmount(burntFee)); result.pushKV("auctionburn", ValueFromAmount(auctionFee)); - result.pushKV("paybackburn", ValueFromAmount(paybackFee)); + result.pushKV("paybackburn", AmountsToJSON(paybackFee.balances)); result.pushKV("dexfeetokens", AmountsToJSON(dexfeeburn.balances)); result.pushKV("dfipaybackfee", ValueFromAmount(dfiPaybackFee)); diff --git a/src/validation.cpp b/src/validation.cpp index 4a7246625f3..b80e500da58 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2880,7 +2880,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl } UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight); } - + int64_t nTime3 = GetTimeMicros(); nTimeConnect += nTime3 - nTime2; LogPrint(BCLog::BENCH, " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(), MILLI * (nTime3 - nTime2), MILLI * (nTime3 - nTime2) / block.vtx.size(), nInputs <= 1 ? 0 : MILLI * (nTime3 - nTime2) / (nInputs-1), nTimeConnect * MICRO, nTimeConnect * MILLI / nBlocksTotal); @@ -3400,7 +3400,7 @@ void CChainState::ProcessLoanEvents(const CBlockIndex* pindex, CCustomCSView& ca auto penaltyAmount = MultiplyAmounts(batch->loanAmount.nValue, COIN + data.liquidationPenalty); if (bidTokenAmount.nValue < penaltyAmount) { - LogPrintf("WARNING: bidTokenAmount.nValue(%d) < penaltyAmount(%d)\n", + LogPrintf("WARNING: bidTokenAmount.nValue(%d) < penaltyAmount(%d)\n", bidTokenAmount.nValue, penaltyAmount); } // penaltyAmount includes interest, batch as well, so we should put interest back @@ -3409,7 +3409,7 @@ void CChainState::ProcessLoanEvents(const CBlockIndex* pindex, CCustomCSView& ca if (amountToBurn > 0) { CScript tmpAddress(vaultId.begin(), vaultId.end()); view.AddBalance(tmpAddress, {bidTokenAmount.nTokenId, amountToBurn}); - SwapToDFIOverUSD(view, bidTokenAmount.nTokenId, amountToBurn, tmpAddress, + SwapToDFIorDUSD(view, bidTokenAmount.nTokenId, amountToBurn, tmpAddress, chainparams.GetConsensus().burnAddress, pindex->nHeight); } @@ -3427,7 +3427,7 @@ void CChainState::ProcessLoanEvents(const CBlockIndex* pindex, CCustomCSView& ca CScript tmpAddress(vaultId.begin(), vaultId.end()); view.AddBalance(tmpAddress, {bidTokenAmount.nTokenId, amountToFill}); - SwapToDFIOverUSD(view, bidTokenAmount.nTokenId, amountToFill, tmpAddress, tmpAddress, pindex->nHeight); + SwapToDFIorDUSD(view, bidTokenAmount.nTokenId, amountToFill, tmpAddress, tmpAddress, pindex->nHeight); auto amount = view.GetBalance(tmpAddress, DCT_ID{0}); view.SubBalance(tmpAddress, amount); view.AddVaultCollateral(vaultId, amount); @@ -3882,11 +3882,11 @@ size_t RewardConsolidationWorkersCount() { // Note: Be careful with lambda captures and default args. GCC 11.2.0, appears the if the captures are // unused in the function directly, but inside the lambda, it completely disassociates them from the fn // possibly when the lambda is lifted up and with default args, ends up inling the default arg -// completely. TODO: verify with smaller test case. +// completely. TODO: verify with smaller test case. // But scenario: If `interruptOnShutdown` is set as default arg to false, it will never be set true // on the below as it's inlined by gcc 11.2.0 on Ubuntu 22.04 incorrectly. Behavior is correct -// in lower versions of gcc or across clang. -void ConsolidateRewards(CCustomCSView &view, int height, +// in lower versions of gcc or across clang. +void ConsolidateRewards(CCustomCSView &view, int height, const std::vector> &items, bool interruptOnShutdown, int numWorkers) { int nWorkers = numWorkers < 1 ? RewardConsolidationWorkersCount() : numWorkers; auto rewardsTime = GetTimeMicros(); @@ -4028,7 +4028,7 @@ static Res PoolSplits(CCustomCSView& view, CAmount& totalBalance, ATTRIBUTES& at balancesToMigrate.size(), totalAccounts, nWorkers); // Largest first to make sure we are over MINIMUM_LIQUIDITY on first call to AddLiquidity - std::sort(balancesToMigrate.begin(), balancesToMigrate.end(), + std::sort(balancesToMigrate.begin(), balancesToMigrate.end(), [](const std::pair&a, const std::pair& b){ return a.second > b.second; }); @@ -4265,9 +4265,9 @@ static Res VaultSplits(CCustomCSView& view, ATTRIBUTES& attributes, const DCT_ID auto oldTokenAmount = CTokenAmount{oldTokenId, amount}; auto newTokenAmount = CTokenAmount{newTokenId, newAmount}; - LogPrint(BCLog::TOKENSPLIT, "TokenSplit: V Loan (%s: %s => %s)\n", + LogPrint(BCLog::TOKENSPLIT, "TokenSplit: V Loan (%s: %s => %s)\n", vaultId.ToString(), oldTokenAmount.ToString(), newTokenAmount.ToString()); - + res = view.AddLoanToken(vaultId, newTokenAmount); if (!res) { return res; @@ -4356,7 +4356,7 @@ static Res VaultSplits(CCustomCSView& view, ATTRIBUTES& attributes, const DCT_ID value.loanInterest = newLoanInterest; LogPrint(BCLog::TOKENSPLIT, "TokenSplit: V AuctionL (%s,%d: %s => %s, %d => %d)\n", - key.first.ToString(), key.second, oldLoanAmount.ToString(), + key.first.ToString(), key.second, oldLoanAmount.ToString(), newLoanAmount.ToString(), oldInterest, newLoanInterest); } diff --git a/test/functional/feature_loan_basics.py b/test/functional/feature_loan_basics.py index 89534152336..9f4e024acd0 100755 --- a/test/functional/feature_loan_basics.py +++ b/test/functional/feature_loan_basics.py @@ -370,7 +370,7 @@ def run_test(self): # loan payback burn vaultInfo = self.nodes[0].getvault(vaultId) - assert_equal(self.nodes[0].getburninfo()['paybackburn'], Decimal('0.00186822')) + assert_equal(self.nodes[0].getburninfo()['paybackburn'], ['0.00186822@' + symbolDFI]) assert_equal(sorted(vaultInfo['loanAmounts']), sorted(['0.50000057@' + symbolTSLA, '1.00000133@' + symbolGOOGL])) try: @@ -408,7 +408,7 @@ def run_test(self): vaultInfo = self.nodes[0].getvault(vaultId) assert_equal(vaultInfo['loanAmounts'], []) assert_equal(sorted(self.nodes[0].listaccounthistory(account0)[0]['amounts']), sorted(['-1.00001463@GOOGL', '-0.50000627@TSLA'])) - assert_greater_than_or_equal(self.nodes[0].getburninfo()['paybackburn'], Decimal('0.00443685')) + assert_greater_than_or_equal(self.nodes[0].getburninfo()['paybackburn'], ['0.00443685@' + symbolDFI]) for interest in self.nodes[0].getinterest('LOAN150'): if interest['token'] == symbolTSLA: diff --git a/test/functional/feature_loan_payback_dfi.py b/test/functional/feature_loan_payback_dfi.py index 0bcfa689f69..d41fcaeefaa 100755 --- a/test/functional/feature_loan_payback_dfi.py +++ b/test/functional/feature_loan_payback_dfi.py @@ -19,8 +19,8 @@ def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', - '-fortcanningheight=50', '-fortcanninghillheight=50', '-eunosheight=50', '-txindex=1'] + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-eunosheight=50', + '-fortcanningheight=50', '-fortcanninghillheight=50', '-fortcanningroadheight=196', '-fortcanninggardensheight=200', '-debug=loan', '-txindex=1'] ] def run_test(self): @@ -46,6 +46,7 @@ def run_test(self): idBTC = list(self.nodes[0].gettoken(symbolBTC).keys())[0] self.nodes[0].utxostoaccount({account0: "1000@" + symbolDFI}) + self.nodes[0].minttokens("500@BTC") oracle_address1 = self.nodes[0].getnewaddress("", "legacy") price_feeds1 = [ @@ -346,6 +347,203 @@ def run_test(self): assert_equal(Decimal(balanceDUSDBefore) - Decimal(balanceDUSDAfter), Decimal('5.00022832')) assert_equal(Decimal(balanceDFIBefore) - Decimal(balanceDFIAfter), Decimal('10')) + # Move to FCG fork + self.nodes[0].generate(200 - self.nodes[0].getblockcount()) + + idTSLA = list(self.nodes[0].gettoken(symbolTSLA).keys())[0] + + # create pool DUSD-DFI + self.nodes[0].createpoolpair({ + "tokenA": idTSLA, + "tokenB": iddUSD, + "commission": Decimal('0.002'), + "status": True, + "ownerAddress": poolOwner, + "pairSymbol": "TSLA-DUSD", + }) + self.nodes[0].createpoolpair({ + "tokenA": idBTC, + "tokenB": iddUSD, + "commission": Decimal('0.002'), + "status": True, + "ownerAddress": poolOwner, + "pairSymbol": "BTC-DUSD", + }) + self.nodes[0].generate(1) + + self.nodes[0].addpoolliquidity( + {account0: ["1@" + symbolTSLA, "10@" + symboldUSD]}, account0) + self.nodes[0].addpoolliquidity( + {account0: ["100@" + symbolBTC, "1000@" + symboldUSD]}, account0) + self.nodes[0].generate(1) + + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206a/direct_interest_dusd_burn':'true', 'v0/token/'+idTSLA+'/loan_payback/'+idBTC: 'true', 'v0/token/'+idTSLA+'/loan_payback/'+iddUSD: 'true'}}) + self.nodes[0].generate(1) + + burnAddress = "mfburnZSAM7Gs1hpDeNaMotJXSGA7edosG" + + [balanceDFIBefore, _] = self.nodes[0].getaccount(burnAddress)[0].split('@') + assert_equal(len(self.nodes[0].getaccount(burnAddress)), 1) + + burn_before = self.nodes[0].getburninfo()['paybackburn'] + + self.nodes[0].paybackloan({ + 'vaultId': vaultId2, + 'from': account0, + 'amounts': ["1@TSLA"] + }) + self.nodes[0].generate(1) + + burn_after = self.nodes[0].getburninfo()['paybackburn'] + + dusd_amount = '0.00216423' + + assert_equal(burn_before[0], burn_after[0]) + assert_equal(burn_after[1], f'{dusd_amount}@{symboldUSD}') + + [balanceDFIAfter, _] = self.nodes[0].getaccount(burnAddress)[0].split('@') + [balanceDUSDAfter, _] = self.nodes[0].getaccount(burnAddress)[1].split('@') + assert_equal(Decimal(balanceDUSDAfter), Decimal(dusd_amount)) + assert_equal(Decimal(balanceDFIAfter) - Decimal(balanceDFIBefore), Decimal('0')) + + balanceDUSDBefore = balanceDUSDAfter + balanceDFIBefore = balanceDFIAfter + burn_before = burn_after + + self.nodes[0].paybackloan({ + 'vaultId': vaultId2, + 'from': account0, + 'loans': [ + { + 'dToken': idTSLA, + 'amounts': "1@BTC" + }, + ] + }) + self.nodes[0].generate(1) + + burn_after = self.nodes[0].getburninfo()['paybackburn'] + + assert_equal(burn_after[0], f'431.96962411@{symbolDFI}') + assert_equal(burn_after[1], burn_before[1]) + + [balanceDFIAfter, _] = self.nodes[0].getaccount(burnAddress)[0].split('@') + [balanceDUSDAfter, _] = self.nodes[0].getaccount(burnAddress)[1].split('@') + assert_equal(Decimal(balanceDUSDAfter) - Decimal(balanceDUSDBefore), Decimal('0')) + assert_equal(Decimal(balanceDFIAfter) - Decimal(balanceDFIBefore), Decimal('0.95477661')) + + balanceDUSDBefore = balanceDUSDAfter + balanceDFIBefore = balanceDFIAfter + burn_before = burn_after + + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206a/direct_loan_dusd_burn':'true'}}) + self.nodes[0].generate(1) + + self.nodes[0].paybackloan({ + 'vaultId': vaultId2, + 'from': account0, + 'loans': [ + { + 'dToken': idTSLA, + 'amounts': "1@BTC" + }, + ] + }) + self.nodes[0].generate(1) + + burn_after = self.nodes[0].getburninfo()['paybackburn'] + + assert_equal(burn_after[0], burn_before[0]) + assert_equal(burn_after[1], f'9.69017531@{symboldUSD}') + + [balanceDFIAfter, _] = self.nodes[0].getaccount(burnAddress)[0].split('@') + [balanceDUSDAfter, _] = self.nodes[0].getaccount(burnAddress)[1].split('@') + assert_equal(Decimal(balanceDUSDAfter) - Decimal(balanceDUSDBefore), Decimal('9.68801108')) + assert_equal(Decimal(balanceDFIAfter) - Decimal(balanceDFIBefore), Decimal('0')) + + balanceDUSDBefore = balanceDUSDAfter + balanceDFIBefore = balanceDFIAfter + burn_before = burn_after + + self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/' + idTSLA + '/payback_dfi':'true'}}) + self.nodes[0].generate(1) + + self.nodes[0].paybackloan({ + 'vaultId': vaultId2, + 'from': account0, + 'loans': [ + { + 'dToken': idTSLA, + 'amounts': "1@DFI" + }, + ] + }) + self.nodes[0].generate(1) + + burn_after = self.nodes[0].getburninfo()['paybackburn'] + + assert_equal(burn_after[0], f'432.96962411@{symbolDFI}') + assert_equal(burn_after[1], burn_before[1]) + + [balanceDFIAfter, _] = self.nodes[0].getaccount(burnAddress)[0].split('@') + [balanceDUSDAfter, _] = self.nodes[0].getaccount(burnAddress)[1].split('@') + assert_equal(Decimal(balanceDUSDAfter) - Decimal(balanceDUSDBefore), Decimal('0')) + assert_equal(Decimal(balanceDFIAfter) - Decimal(balanceDFIBefore), Decimal('1.00000000')) + + balanceDUSDBefore = balanceDUSDAfter + balanceDFIBefore = balanceDFIAfter + burn_before = burn_after + + self.nodes[0].takeloan({ + 'vaultId': vaultId2, + 'amounts': "100@DUSD" + }) + self.nodes[0].generate(10) + + self.nodes[0].paybackloan({ + 'vaultId': vaultId2, + 'from': account0, + 'amounts': ["10@DUSD"] + }) + self.nodes[0].generate(1) + + burn_after = self.nodes[0].getburninfo()['paybackburn'] + + assert_equal(burn_after[0], burn_before[0]) + assert_equal(burn_after[1], f'9.69131687@{symboldUSD}') + + [balanceDFIAfter, _] = self.nodes[0].getaccount(burnAddress)[0].split('@') + [balanceDUSDAfter, _] = self.nodes[0].getaccount(burnAddress)[1].split('@') + + assert_equal(Decimal(balanceDUSDAfter) - Decimal(balanceDUSDBefore), Decimal('0.00114156')) + assert_equal(Decimal(balanceDFIAfter) - Decimal(balanceDFIBefore), Decimal('0')) + + balanceDUSDBefore = balanceDUSDAfter + balanceDFIBefore = balanceDFIAfter + burn_before = burn_after + + self.nodes[0].paybackloan({ + 'vaultId': vaultId2, + 'from': account0, + 'loans': [ + { + 'dToken': idTSLA, + 'amounts': "10@DUSD" + }, + ] + }) + self.nodes[0].generate(1) + + burn_after = self.nodes[0].getburninfo()['paybackburn'] + + assert_equal(burn_after[0], burn_before[0]) + assert_equal(burn_after[1], f'19.69131687@{symboldUSD}') + + [balanceDFIAfter, _] = self.nodes[0].getaccount(burnAddress)[0].split('@') + [balanceDUSDAfter, _] = self.nodes[0].getaccount(burnAddress)[1].split('@') + + assert_equal(Decimal(balanceDUSDAfter) - Decimal(balanceDUSDBefore), Decimal('10')) + assert_equal(Decimal(balanceDFIAfter) - Decimal(balanceDFIBefore), Decimal('0')) if __name__ == '__main__': PaybackDFILoanTest().main() diff --git a/test/functional/feature_loan_payback_dfi_v2.py b/test/functional/feature_loan_payback_dfi_v2.py index 0ae23da1332..f7f03f7a2bb 100755 --- a/test/functional/feature_loan_payback_dfi_v2.py +++ b/test/functional/feature_loan_payback_dfi_v2.py @@ -320,8 +320,6 @@ def setgov_attribute_to_true_and_payback_with_dfi(self): vaultBefore = self.nodes[0].getvault(self.vaultId1) [amountBefore, _] = vaultBefore['loanAmounts'][0].split('@') - old_info = self.nodes[0].getburninfo() - # Partial loan payback in DFI self.nodes[0].paybackloan({ 'vaultId': self.vaultId1, @@ -333,7 +331,8 @@ def setgov_attribute_to_true_and_payback_with_dfi(self): info = self.nodes[0].getburninfo() assert_equal(info['dfipaybackfee'], Decimal('0.01000000')) # paybackfee defaults to 1% of total payback -> 0.01 DFI assert_equal(info['dfipaybacktokens'], ['3.96000000@DUSD']) # 4 - penalty (0.01DFI->0.04USD) - assert_equal(info['paybackburn'], old_info['paybackburn'] + Decimal('1')) + [new_amount, _] = info['paybackburn'][0].split('@') + assert_equal(Decimal(new_amount), Decimal('1')) vaultAfter = self.nodes[0].getvault(self.vaultId1) [amountAfter, _] = vaultAfter['loanAmounts'][0].split('@') @@ -348,7 +347,7 @@ def test_5pct_penalty(self): vaultBefore = self.nodes[0].getvault(self.vaultId1) [amountBefore, _] = vaultBefore['loanAmounts'][0].split('@') - old_info = self.nodes[0].getburninfo() + [old_amount, _] = self.nodes[0].getburninfo()['paybackburn'][0].split('@') # Partial loan payback in DFI self.nodes[0].paybackloan({ @@ -361,7 +360,8 @@ def test_5pct_penalty(self): info = self.nodes[0].getburninfo() assert_equal(info['dfipaybackfee'], Decimal('0.06000000')) assert_equal(info['dfipaybacktokens'], ['7.76000000@DUSD']) - assert_equal(info['paybackburn'], old_info['paybackburn'] + Decimal('1')) + [new_amount, _] = info['paybackburn'][0].split('@') + assert_equal(Decimal(new_amount), Decimal(old_amount) + Decimal('1')) vaultAfter = self.nodes[0].getvault(self.vaultId1) [amountAfter, _] = vaultAfter['loanAmounts'][0].split('@') @@ -374,7 +374,7 @@ def overpay_loan_in_DFI(self): [amountBefore, _] = vaultBefore['loanAmounts'][0].split('@') [balanceDFIBefore, _] = self.nodes[0].getaccount(self.addr_DFI)[0].split('@') - old_info = self.nodes[0].getburninfo() + [old_amount, _] = self.nodes[0].getburninfo()['paybackburn'][0].split('@') self.nodes[0].paybackloan({ 'vaultId': self.vaultId1, @@ -386,7 +386,8 @@ def overpay_loan_in_DFI(self): info = self.nodes[0].getburninfo() assert_equal(info['dfipaybackfee'], Decimal('1.27368435')) assert_equal(info['dfipaybacktokens'], ['100.00001113@DUSD']) # Total loan in vault1 + previous dfipaybacktokens - assert_equal(info['paybackburn'], old_info['paybackburn'] + Decimal('24.27368714')) + [new_amount, _] = info['paybackburn'][0].split('@') + assert_equal(Decimal(new_amount), Decimal(old_amount) + Decimal('24.27368714')) attribs = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] assert_equal(attribs['v0/live/economy/dfi_payback_tokens'], ['1.27368435@DFI', '100.00001113@DUSD']) @@ -405,7 +406,7 @@ def take_new_loan_payback_exact_amount_in_DFI(self): }) self.nodes[0].generate(10) - old_info = self.nodes[0].getburninfo() + [old_amount, _] = self.nodes[0].getburninfo()['paybackburn'][0].split('@') [balanceDFIBefore, _] = self.nodes[0].getaccount(self.addr_DFI)[0].split('@') self.nodes[0].paybackloan({ @@ -418,7 +419,8 @@ def take_new_loan_payback_exact_amount_in_DFI(self): info = self.nodes[0].getburninfo() assert_equal(info['dfipaybackfee'], Decimal('2.58947407')) assert_equal(info['dfipaybacktokens'], ['200.00003016@DUSD']) - assert_equal(info['paybackburn'], old_info['paybackburn'] + Decimal('26.31579449')) + [new_amount, _] = info['paybackburn'][0].split('@') + assert_equal(Decimal(new_amount), Decimal(old_amount) + Decimal('26.31579449')) vaultAfter = self.nodes[0].getvault(self.vaultId1) [balanceDFIAfter, _] = self.nodes[0].getaccount(self.addr_DFI)[0].split('@') @@ -615,7 +617,7 @@ def payback_TSLA_with_1_dBTC(self): vaultBefore = self.nodes[0].getvault(self.vaultId3) [amountBefore, _] = vaultBefore['loanAmounts'][0].split('@') - old_info = self.nodes[0].getburninfo() + [old_amount, _] = self.nodes[0].getburninfo()['paybackburn'][0].split('@') self.nodes[0].paybackloan({ 'vaultId': self.vaultId3, @@ -638,7 +640,8 @@ def payback_TSLA_with_1_dBTC(self): info = self.nodes[0].getburninfo() assert_equal(info['paybackfees'], ['0.12500000@BTC']) assert_equal(info['paybacktokens'], ['3750000.00000000@TSLA']) - assert_equal(info['paybackburn'], old_info['paybackburn'] + Decimal('6209.54767138')) + [new_amount, _] = info['paybackburn'][0].split('@') + assert_equal(Decimal(new_amount), Decimal(old_amount) + Decimal('6209.54767138')) def payback_dUSD_with_dUSD(self): self.nodes[0].takeloan({ @@ -685,7 +688,7 @@ def payback_TSLA_with_1sat_dBTC(self): }) self.nodes[0].generate(1) - old_info = self.nodes[0].getburninfo() + [old_amount, _] = self.nodes[0].getburninfo()['paybackburn'][0].split('@') self.nodes[0].paybackloan({ 'vaultId': self.vaultId4, @@ -707,7 +710,8 @@ def payback_TSLA_with_1sat_dBTC(self): info = self.nodes[0].getburninfo() assert_equal(info['paybackfees'], ['0.12500000@BTC']) assert_equal(info['paybacktokens'], ['3750000.00000002@TSLA']) - assert_equal(info['paybackburn'], old_info['paybackburn'] + Decimal('0.00012413')) + [new_amount, _] = info['paybackburn'][0].split('@') + assert_equal(Decimal(new_amount), Decimal(old_amount) + Decimal('0.00012413')) def multipayback_DUSD_with_DFI_and_DUSD(self): self.vaultId5 = self.nodes[0].createvault(self.account0, 'LOAN200') diff --git a/test/functional/feature_setgov.py b/test/functional/feature_setgov.py index 78484724884..d341031538d 100755 --- a/test/functional/feature_setgov.py +++ b/test/functional/feature_setgov.py @@ -639,7 +639,7 @@ def run_test(self): assert_raises_rpc_error(-32600, "ATTRIBUTES: Cannot be set before FortCanningCrunch", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_minting_enabled':'true'}}) assert_raises_rpc_error(-32600, "ATTRIBUTES: Cannot be set before FortCanningCrunch", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_minting_interest':'5.00000000'}}) - # Move to GW fork + # Move to FCC fork self.nodes[0].generate(1200 - self.nodes[0].getblockcount()) # Check errors @@ -770,6 +770,16 @@ def run_test(self): # Test invalid calls assert_raises_rpc_error(-5, "Fee direction value must be both, in or out", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/poolpairs/3/token_a_fee_direction': 'invalid'}}) + assert_raises_rpc_error(-5, "Boolean value must be either \"true\" or \"false\"", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206a/direct_interest_dusd_burn':'not_a_bool'}}) + assert_raises_rpc_error(-5, "Boolean value must be either \"true\" or \"false\"", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206a/direct_loan_dusd_burn':'not_a_bool'}}) + + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206a/direct_interest_dusd_burn':'true', 'v0/params/dfip2206a/direct_loan_dusd_burn':'true'}}) + self.nodes[0].generate(1) + + # Verify FCR results + result = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(result['v0/params/dfip2206a/direct_interest_dusd_burn'], 'true') + assert_equal(result['v0/params/dfip2206a/direct_loan_dusd_burn'], 'true') # Set fee direction Gov vars self.nodes[0].setgov({"ATTRIBUTES":{