From f0f620e7a996cce33f751f6d316531c603176ccb Mon Sep 17 00:00:00 2001 From: Mihailo Milenkovic Date: Mon, 24 Jan 2022 21:28:04 +0100 Subject: [PATCH] Payback of DUSD loan with DFI (#1024) * Payback of DUSD loan with DFI (#1024) Co-authored-by: jouzo Co-authored-by: Peter Bushnell Co-authored-by: Prasanna Loganathar --- src/masternodes/mn_checks.cpp | 121 ++++++-- .../feature_loan_dusd_as_collateral.py | 138 ++++++--- test/functional/feature_loan_payback_dfi.py | 268 ++++++++++++++++++ test/functional/test_runner.py | 1 + test/lint/lint-circular-dependencies.sh | 1 + 5 files changed, 476 insertions(+), 53 deletions(-) create mode 100755 test/functional/feature_loan_payback_dfi.py diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 8d5c2b09db..9bf291eb71 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -2825,9 +2825,71 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor if (!IsVaultPriceValid(mnview, obj.vaultId, height)) return Res::Err("Cannot payback loan while any of the asset's price is invalid"); + auto allowDFIPayback = false; + std::map attrs; + auto tokenDUSD = mnview.GetToken("DUSD"); + if (tokenDUSD) { + const auto pAttributes = mnview.GetAttributes(); + if (pAttributes) { + attrs = pAttributes->attributes; + CDataStructureV0 activeKey{AttributeTypes::Token, tokenDUSD->first.v, TokenKeys::PaybackDFI}; + try { + const auto& value = attrs.at(activeKey); + auto valueV0 = boost::get(&value); + if (valueV0) { + const auto active = boost::get(valueV0); + if (active != nullptr && *active) { + allowDFIPayback = true; + } + } + } catch (const std::out_of_range&) {} + } + } + for (const auto& kv : obj.amounts.balances) { DCT_ID tokenId = kv.first; + auto paybackAmount = kv.second; + CAmount dfiUSDPrice{0}; + + if (height >= Params().GetConsensus().FortCanningHillHeight && kv.first == DCT_ID{0}) + { + if (!allowDFIPayback || !tokenDUSD) { + return Res::Err("Payback of DUSD loans with DFI not currently active"); + } + + // Get DFI price in USD + const std::pair dfiUsd{"DFI","USD"}; + bool useNextPrice{false}, requireLivePrice{true}; + const auto resVal = mnview.GetValidatedIntervalPrice(dfiUsd, useNextPrice, requireLivePrice); + if (!resVal) { + return std::move(resVal); + } + + // Apply penalty + CAmount penalty{99000000}; // Update from Gov var + CDataStructureV0 penaltyKey{AttributeTypes::Token, tokenDUSD->first.v, TokenKeys::PaybackDFIFeePCT}; + try { + const auto& value = attrs.at(penaltyKey); + auto valueV0 = boost::get(&value); + if (valueV0) { + if (auto storedPenalty = boost::get(valueV0)) { + penalty = COIN - *storedPenalty; + } + } + } catch (const std::out_of_range&) {} + dfiUSDPrice = MultiplyAmounts(*resVal.val, penalty); + + // Set tokenId to DUSD + tokenId = tokenDUSD->first; + + // Calculate the DFI amount in DUSD + paybackAmount = MultiplyAmounts(dfiUSDPrice, kv.second); + if (dfiUSDPrice > COIN && paybackAmount < kv.second) { + return Res::Err("Value/price too high (%s/%s)", GetDecimaleString(kv.second), GetDecimaleString(dfiUSDPrice)); + } + } + auto loanToken = mnview.GetLoanTokenByID(tokenId); if (!loanToken) return Res::Err("Loan token with id (%s) does not exist!", tokenId.ToString()); @@ -2846,17 +2908,19 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor LogPrint(BCLog::LOAN,"CLoanPaybackMessage()->%s->", loanToken->symbol); /* Continued */ auto subInterest = TotalInterest(*rate, height); - auto subLoan = kv.second - subInterest; + auto subLoan = paybackAmount - subInterest; - if (kv.second < subInterest) + if (paybackAmount < subInterest) { - subInterest = kv.second; + subInterest = paybackAmount; subLoan = 0; } else if (it->second - subLoan < 0) + { subLoan = it->second; + } - res = mnview.SubLoanToken(obj.vaultId, CTokenAmount{kv.first, subLoan}); + res = mnview.SubLoanToken(obj.vaultId, CTokenAmount{tokenId, subLoan}); if (!res) return res; @@ -2875,24 +2939,49 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor return Res::Err("Cannot payback this amount of loan for %s, either payback full amount or less than this amount!", loanToken->symbol); } - res = mnview.SubMintedTokens(loanToken->creationTx, subLoan); - if (!res) - return res; - CalculateOwnerRewards(obj.from); - // subtract loan amount first, interest is burning below - res = mnview.SubBalance(obj.from, CTokenAmount{kv.first, subLoan}); - if (!res) - return res; - // burn interest Token->USD->DFI->burnAddress - if (subInterest) + if (height < Params().GetConsensus().FortCanningHillHeight || kv.first != DCT_ID{0}) { - LogPrint(BCLog::LOAN, "CLoanTakeLoanMessage(): Swapping %s interest to DFI - %lld, height - %d\n", loanToken->symbol, subInterest, height); - res = SwapToDFIOverUSD(mnview, kv.first, subInterest, obj.from, consensus.burnAddress, height); + res = mnview.SubMintedTokens(loanToken->creationTx, subLoan); + if (!res) + return res; + + // subtract loan amount first, interest is burning below + LogPrint(BCLog::LOAN, "CLoanTakeLoanMessage(): Sub loan from balance - %lld, height - %d\n", subLoan, height); + res = mnview.SubBalance(obj.from, CTokenAmount{tokenId, subLoan}); if (!res) return res; + + // burn interest Token->USD->DFI->burnAddress + if (subInterest) + { + LogPrint(BCLog::LOAN, "CLoanTakeLoanMessage(): Swapping %s interest to DFI - %lld, height - %d\n", loanToken->symbol, subInterest, height); + res = SwapToDFIOverUSD(mnview, tokenId, subInterest, obj.from, consensus.burnAddress, height); + } + } + else + { + CAmount subInDFI; + // if DFI payback overpay loan and interest amount + if (paybackAmount > subLoan + subInterest) + { + subInDFI = DivideAmounts(subLoan + subInterest, dfiUSDPrice); + if (MultiplyAmounts(subInDFI, dfiUSDPrice) != subLoan + subInterest) { + subInDFI += 1; + } + } + else + { + subInDFI = kv.second; + } + + LogPrint(BCLog::LOAN, "CLoanTakeLoanMessage(): Burning interest and loan in DFI directly - %lld (%lld DFI), height - %d\n", subLoan + subInterest, subInDFI, height); + res = TransferTokenBalance(DCT_ID{0}, subInDFI, obj.from, consensus.burnAddress); } + + if (!res) + return res; } return Res::Ok(); diff --git a/test/functional/feature_loan_dusd_as_collateral.py b/test/functional/feature_loan_dusd_as_collateral.py index 52c3127a50..18d0775b29 100755 --- a/test/functional/feature_loan_dusd_as_collateral.py +++ b/test/functional/feature_loan_dusd_as_collateral.py @@ -8,6 +8,7 @@ from test_framework.test_framework import DefiTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error +from decimal import Decimal import time class LoanDUSDCollateralTest (DefiTestFramework): @@ -15,15 +16,15 @@ def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-fortcanningheight=50', '-fortcanninghillheight=500', '-eunosheight=50', '-txindex=1']] + ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanninghillheight=200', '-jellyfish_regtest=1']] def run_test(self): - assert_equal(len(self.nodes[0].listtokens()), 1) # only one token == DFI - print("Generating initial chain...") self.nodes[0].generate(120) - symbolDFI = "DFI" - symbolDUSD = "DUSD" + mn_address = self.nodes[0].get_genesis_keys().ownerAuthAddress + + symbol_dfi = "DFI" + symbol_dusd = "DUSD" self.nodes[0].setloantoken({ 'symbol': "DUSD", @@ -34,70 +35,133 @@ def run_test(self): }) self.nodes[0].generate(1) - oracleAddress = self.nodes[0].getnewaddress("", "legacy") - price_feeds1 = [ + id_usd = list(self.nodes[0].gettoken(symbol_dusd).keys())[0] + + # Mint DUSD + self.nodes[0].minttokens("100000@DUSD") + self.nodes[0].generate(1) + + # Create DFI tokens + self.nodes[0].utxostoaccount({mn_address: "100000@" + symbol_dfi}) + self.nodes[0].generate(1) + + # Create pool pair + self.nodes[0].createpoolpair({ + "tokenA": symbol_dfi, + "tokenB": symbol_dusd, + "commission": 0, + "status": True, + "ownerAddress": mn_address + }) + self.nodes[0].generate(1) + + # Add pool liquidity + self.nodes[0].addpoolliquidity({ + mn_address: [ + '10000@' + symbol_dfi, + '8000@' + symbol_dusd] + }, mn_address) + self.nodes[0].generate(1) + + # Set up Oracles + oracle_address = self.nodes[0].getnewaddress("", "legacy") + price_feed = [ {"currency": "USD", "token": "DFI"} ] - oracle_id1 = self.nodes[0].appointoracle(oracleAddress, price_feeds1, 10) + + oracle = self.nodes[0].appointoracle(oracle_address, price_feed, 10) self.nodes[0].generate(1) - oracle1_prices = [ + oracle_prices = [ {"currency": "USD", "tokenAmount": "1@DFI"}, ] - mock_time = int(time.time()) - self.nodes[0].setmocktime(mock_time) - self.nodes[0].setoracledata(oracle_id1, mock_time, oracle1_prices) - + self.nodes[0].setoracledata(oracle, int(time.time()), oracle_prices) self.nodes[0].generate(1) + # Set collateral tokens self.nodes[0].setcollateraltoken({ - 'token': symbolDFI, + 'token': symbol_dfi, 'factor': 1, 'fixedIntervalPriceId': "DFI/USD" }) - tokenFactorDUSD = 0.9 - activateAfterBlock = self.nodes[0].getblockcount() + 50 + token_factor_dusd = 0.99 + activate = self.nodes[0].getblockcount() + 50 self.nodes[0].setcollateraltoken({ - 'token': "DUSD", - 'factor': tokenFactorDUSD, + 'token': symbol_dusd, + 'factor': token_factor_dusd, 'fixedIntervalPriceId': "DUSD/USD", - 'activateAfterBlock': activateAfterBlock + 'activateAfterBlock': activate }) self.nodes[0].generate(1) - self.nodes[0].createloanscheme(200, 1, 'LOAN001') + # Create loan scheme + self.nodes[0].createloanscheme(150, 1, 'LOAN001') self.nodes[0].generate(1) - vaultAddress = self.nodes[0].getnewaddress('', 'legacy') - vaultId = self.nodes[0].createvault(vaultAddress, 'LOAN001') + # Create vault + vault_address = self.nodes[0].getnewaddress('', 'legacy') + vault_id = self.nodes[0].createvault(vault_address, 'LOAN001') self.nodes[0].generate(1) - amountDFI = 500 - amountDUSD = 250 - - self.nodes[0].utxostoaccount({vaultAddress: str(amountDFI) + "@" + symbolDFI}) + # Fund vault address with DUSD and DFI + collateral = 2000 + loan_dusd = 1000 + self.nodes[0].accounttoaccount(mn_address, {vault_address: str(collateral) + "@" + symbol_dusd}) + self.nodes[0].accounttoaccount(mn_address, {vault_address: str(collateral) + "@" + symbol_dfi}) self.nodes[0].generate(1) - self.nodes[0].deposittovault(vaultId, vaultAddress, str(amountDFI) + "@" + symbolDFI) - self.nodes[0].generate(1) + # DUSD is not active as a collateral token yet + assert_raises_rpc_error(-32600, "Collateral token with id (1) does not exist!", self.nodes[0].deposittovault, vault_id, vault_address, str(collateral) + "@" + symbol_dusd) + + # Activates DUSD as collateral token + self.nodes[0].generate(activate - self.nodes[0].getblockcount()) - self.nodes[0].takeloan({ "vaultId": vaultId, "amounts": str(amountDUSD) + "@" + symbolDUSD }) + # Deposit DUSD and DFI to vault + self.nodes[0].deposittovault(vault_id, vault_address, str(collateral) + "@" + symbol_dusd) + self.nodes[0].deposittovault(vault_id, vault_address, str(collateral) + "@" + symbol_dfi) self.nodes[0].generate(1) - # DUSD is not active as a collateral token yet - assert_raises_rpc_error(-32600, "Collateral token with id (1) does not exist!", self.nodes[0].deposittovault, vaultId, vaultAddress, str(amountDUSD) + "@" + symbolDUSD) + vault = self.nodes[0].getvault(vault_id) + assert("DUSD" in vault['collateralAmounts'][1]) + assert_equal(vault['collateralValue'], collateral * token_factor_dusd + collateral) - self.nodes[0].generate(50) # Activates DUSD as collateral token + # Move to FortCanningHill fork + self.nodes[0].generate(200 - self.nodes[0].getblockcount()) - # Should be able to deposit DUSD to vault - self.nodes[0].deposittovault(vaultId, vaultAddress, str(amountDUSD) + "@" + symbolDUSD) + # Enable loan payback + self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/' + id_usd + '/payback_dfi':'true'}}) self.nodes[0].generate(1) - vault = self.nodes[0].getvault(vaultId) + # Take DUSD loan + self.nodes[0].takeloan({ "vaultId": vault_id, "amounts": str(loan_dusd) + "@" + symbol_dusd }) + self.nodes[0].generate(1) - assert("DUSD" in vault['collateralAmounts'][1]) - assert_equal(vault['collateralValue'], amountDUSD * tokenFactorDUSD + amountDFI) + # Loan value loan amount + interest + vault = self.nodes[0].getvault(vault_id) + assert_equal(vault['loanValue'], Decimal(loan_dusd) + vault['interestValue']) + + # Swap DUSD from loan to DFI + self.nodes[0].poolswap({ + "from": vault_address, + "tokenFrom": symbol_dusd, + "amountFrom": loan_dusd, + "to": vault_address, + "tokenTo": symbol_dfi + }) + self.nodes[0].generate(1) + + # Payback loan with DFI + [dfi_balance, _] = self.nodes[0].getaccount(vault_address)[0].split('@') + self.nodes[0].paybackloan({ + 'vaultId': vault_id, + 'from': vault_address, + 'amounts': [dfi_balance + '@' + symbol_dfi]}) + self.nodes[0].generate(1) + + # Loan should be paid back in full + vault = self.nodes[0].getvault(vault_id) + assert_equal(vault['loanValue'], Decimal('0')) if __name__ == '__main__': LoanDUSDCollateralTest().main() diff --git a/test/functional/feature_loan_payback_dfi.py b/test/functional/feature_loan_payback_dfi.py new file mode 100755 index 0000000000..b11a59b341 --- /dev/null +++ b/test/functional/feature_loan_payback_dfi.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2019 The Bitcoin Core developers +# Copyright (c) DeFi Blockchain Developers +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. +"""Test Loan - payback loan.""" + +from test_framework.test_framework import DefiTestFramework +from test_framework.util import assert_equal, assert_raises_rpc_error + +import calendar +import time +from decimal import Decimal, ROUND_UP + + +class PaybackLoanTest (DefiTestFramework): + 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'] + ] + + def run_test(self): + self.nodes[0].generate(150) + + account0 = self.nodes[0].get_genesis_keys().ownerAuthAddress + + symbolDFI = "DFI" + symbolBTC = "BTC" + symboldUSD = "DUSD" + symbolTSLA = "TSLA" + + self.nodes[0].createtoken({ + "symbol": symbolBTC, + "name": "BTC token", + "isDAT": True, + "collateralAddress": account0 + }) + + self.nodes[0].generate(1) + + idDFI = list(self.nodes[0].gettoken(symbolDFI).keys())[0] + idBTC = list(self.nodes[0].gettoken(symbolBTC).keys())[0] + + self.nodes[0].utxostoaccount({account0: "1000@" + symbolDFI}) + + oracle_address1 = self.nodes[0].getnewaddress("", "legacy") + price_feeds1 = [ + {"currency": "USD", "token": "DFI"}, + {"currency": "USD", "token": "BTC"}, + {"currency": "USD", "token": "TSLA"} + ] + oracle_id1 = self.nodes[0].appointoracle( + oracle_address1, price_feeds1, 10) + self.nodes[0].generate(1) + + # feed oracle + oracle1_prices = [ + {"currency": "USD", "tokenAmount": "10@TSLA"}, + {"currency": "USD", "tokenAmount": "10@DFI"}, + {"currency": "USD", "tokenAmount": "10@BTC"} + ] + timestamp = calendar.timegm(time.gmtime()) + self.nodes[0].setoracledata(oracle_id1, timestamp, oracle1_prices) + self.nodes[0].generate(1) + + self.nodes[0].setcollateraltoken({ + 'token': idDFI, + 'factor': 1, + 'fixedIntervalPriceId': "DFI/USD" + }) + + self.nodes[0].setcollateraltoken({ + 'token': idBTC, + 'factor': 1, + 'fixedIntervalPriceId': "BTC/USD"}) + + self.nodes[0].generate(1) + + self.nodes[0].setloantoken({ + 'symbol': symbolTSLA, + 'name': "Tesla stock token", + 'fixedIntervalPriceId': "TSLA/USD", + 'mintable': True, + 'interest': 1 + }) + + self.nodes[0].setloantoken({ + 'symbol': symboldUSD, + 'name': "DUSD stable token", + 'fixedIntervalPriceId': "DUSD/USD", + 'mintable': True, + 'interest': 1 + }) + self.nodes[0].generate(1) + + self.nodes[0].createloanscheme(150, 5, 'LOAN150') + + self.nodes[0].generate(5) + + iddUSD = list(self.nodes[0].gettoken(symboldUSD).keys())[0] + + vaultId = self.nodes[0].createvault(account0, 'LOAN150') + self.nodes[0].generate(1) + + self.nodes[0].deposittovault(vaultId, account0, "400@DFI") + self.nodes[0].generate(1) + + self.nodes[0].takeloan({ + 'vaultId': vaultId, + 'amounts': "2000@" + symboldUSD + }) + self.nodes[0].generate(1) + + poolOwner = self.nodes[0].getnewaddress("", "legacy") + # create pool DUSD-DFI + self.nodes[0].createpoolpair({ + "tokenA": iddUSD, + "tokenB": idDFI, + "commission": Decimal('0.002'), + "status": True, + "ownerAddress": poolOwner, + "pairSymbol": "DUSD-DFI", + }) + self.nodes[0].generate(1) + + self.nodes[0].addpoolliquidity( + {account0: ["30@" + symbolDFI, "300@" + symboldUSD]}, account0) + self.nodes[0].generate(1) + + # Should not be able to payback loan with BTC + assert_raises_rpc_error(-32600, "Loan token with id (1) does not exist!", self.nodes[0].paybackloan, { + 'vaultId': vaultId, + 'from': account0, + 'amounts': "1@BTC" + }) + + # Should not be able to payback loan before DFI payback enabled + assert_raises_rpc_error(-32600, "Payback of DUSD loans with DFI not currently active", self.nodes[0].paybackloan, { + 'vaultId': vaultId, + 'from': account0, + 'amounts': "1@DFI" + }) + + # Disable loan payback + self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/' + iddUSD + '/payback_dfi':'false'}}) + self.nodes[0].generate(1) + + # Should not be able to payback loan before DFI payback enabled + assert_raises_rpc_error(-32600, "Payback of DUSD loans with DFI not currently active", self.nodes[0].paybackloan, { + 'vaultId': vaultId, + 'from': account0, + 'amounts': "1@DFI" + }) + + # Enable loan payback + self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/' + iddUSD + '/payback_dfi':'true'}}) + self.nodes[0].generate(1) + + vaultBefore = self.nodes[0].getvault(vaultId) + [amountBefore, _] = vaultBefore['loanAmounts'][0].split('@') + + # Partial loan payback in DFI + self.nodes[0].paybackloan({ + 'vaultId': vaultId, + 'from': account0, + 'amounts': "1@DFI" + }) + self.nodes[0].generate(1) + + vaultAfter = self.nodes[0].getvault(vaultId) + [amountAfter, _] = vaultAfter['loanAmounts'][0].split('@') + [interestAfter, _] = vaultAfter['interestAmounts'][0].split('@') + + assert_equal(Decimal(amountAfter) - Decimal(interestAfter), (Decimal(amountBefore) - (10 * Decimal('0.99')))) + + # Test 5% penalty + self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/' + iddUSD + '/payback_dfi_fee_pct':'0.05'}}) + self.nodes[0].generate(1) + + vaultBefore = self.nodes[0].getvault(vaultId) + [amountBefore, _] = vaultBefore['loanAmounts'][0].split('@') + + # Partial loan payback in DFI + self.nodes[0].paybackloan({ + 'vaultId': vaultId, + 'from': account0, + 'amounts': "1@DFI" + }) + self.nodes[0].generate(1) + + vaultAfter = self.nodes[0].getvault(vaultId) + [amountAfter, _] = vaultAfter['loanAmounts'][0].split('@') + [interestAfter, _] = vaultAfter['interestAmounts'][0].split('@') + + assert_equal(Decimal(amountAfter) - Decimal(interestAfter), (Decimal(amountBefore) - (10 * Decimal('0.95')))) + + vaultBefore = vaultAfter + [balanceDFIBefore, _] = self.nodes[0].getaccount(account0)[0].split('@') + [amountBefore, _] = vaultBefore['loanAmounts'][0].split('@') + + # Overpay loan payback in DFI + self.nodes[0].paybackloan({ + 'vaultId': vaultId, + 'from': account0, + 'amounts': "250@DFI" + }) + self.nodes[0].generate(1) + + vaultAfter = self.nodes[0].getvault(vaultId) + [balanceDFIAfter, _] = self.nodes[0].getaccount(account0)[0].split('@') + + assert_equal(len(vaultAfter['loanAmounts']), 0) + assert_equal(len(vaultAfter['interestAmounts']), 0) + assert_equal(Decimal(balanceDFIBefore) - Decimal(balanceDFIAfter), (Decimal(amountBefore) / Decimal('9.5')).quantize(Decimal('1E-8'), rounding=ROUND_UP)) + + # Exact amount loan payback in DFI + + # take new loan of 2000DUSD + self.nodes[0].takeloan({ + 'vaultId': vaultId, + 'amounts': "2000@" + symboldUSD + }) + self.nodes[0].generate(10) + + vaultBefore = self.nodes[0].getvault(vaultId) + [amountBefore, _] = vaultBefore['loanAmounts'][0].split('@') + [balanceDFIBefore, _] = self.nodes[0].getaccount(account0)[0].split('@') + + self.nodes[0].paybackloan({ + 'vaultId': vaultId, + 'from': account0, + 'amounts': "210.52871908@DFI" + }) + self.nodes[0].generate(1) + + vaultAfter = self.nodes[0].getvault(vaultId) + [balanceDFIAfter, _] = self.nodes[0].getaccount(account0)[0].split('@') + + assert_equal(len(vaultAfter['loanAmounts']), 0) + assert_equal(len(vaultAfter['interestAmounts']), 0) + assert_equal(Decimal(balanceDFIBefore) - Decimal(balanceDFIAfter), Decimal('210.52871908')) + + # Payback of loan token other than DUSD + vaultId2 = self.nodes[0].createvault(account0, 'LOAN150') + self.nodes[0].generate(1) + + self.nodes[0].deposittovault(vaultId2, account0, "100@DFI") + self.nodes[0].generate(1) + + self.nodes[0].takeloan({ + 'vaultId': vaultId2, + 'amounts': "10@" + symbolTSLA + }) + self.nodes[0].generate(1) + + # Should not be able to payback loan token other than DUSD with DFI + assert_raises_rpc_error(-32600, "There is no loan on token (DUSD) in this vault!", self.nodes[0].paybackloan, { + 'vaultId': vaultId2, + 'from': account0, + 'amounts': "10@DFI" + }) + + +if __name__ == '__main__': + PaybackLoanTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 160dbf21dc..383e5520fb 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -163,6 +163,7 @@ 'feature_loan_setcollateraltoken.py', 'feature_loan_setloantoken.py', 'feature_loan_basics.py', + 'feature_loan_payback_dfi.py', 'feature_loan_listauctions.py', 'feature_loan_auctions.py', 'feature_loan_dusd_as_collateral.py', diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 5d00434790..0ab5a85dda 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -52,6 +52,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "masternodes/govvariables/attributes -> masternodes/masternodes -> masternodes/mn_checks -> masternodes/govvariables/attributes" "masternodes/govvariables/oracle_deviation -> masternodes/gv -> masternodes/govvariables/oracle_deviation" "masternodes/govvariables/lp_splits -> masternodes/gv -> masternodes/govvariables/lp_splits" + "masternodes/govvariables/attributes -> masternodes/masternodes -> masternodes/mn_checks -> masternodes/govvariables/attributes" "masternodes/masternodes -> masternodes/oracles -> masternodes/masternodes" "masternodes/govvariables/loan_daily_reward -> masternodes/masternodes -> validation -> masternodes/govvariables/loan_daily_reward" "masternodes/masternodes -> masternodes/mn_checks -> masternodes/masternodes"