From ddb821ce581ad51a8b41a2dba6806a6865ef01c8 Mon Sep 17 00:00:00 2001 From: jouzo Date: Fri, 26 Nov 2021 15:44:22 +0100 Subject: [PATCH 1/5] Adds possibility to use "*" to auto-select address in placeauctionbid and paybackloan --- src/masternodes/rpc_loan.cpp | 37 ++++++++++++--- src/masternodes/rpc_vault.cpp | 23 ++++++++- test/functional/feature_loan.py | 49 ++++++++++++++++++- test/functional/feature_loan_basics.py | 66 ++++++++++++++++++++++++-- 4 files changed, 161 insertions(+), 14 deletions(-) diff --git a/src/masternodes/rpc_loan.cpp b/src/masternodes/rpc_loan.cpp index a56f6be527c..3ab51623669 100644 --- a/src/masternodes/rpc_loan.cpp +++ b/src/masternodes/rpc_loan.cpp @@ -1124,7 +1124,7 @@ UniValue paybackloan(const JSONRPCRequest& request) { {"metadata", RPCArg::Type::OBJ, RPCArg::Optional::NO, "", { {"vaultId", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Id of vault used for loan"}, - {"from", RPCArg::Type::STR, RPCArg::Optional::NO, "Address containing repayment tokens"}, + {"from", RPCArg::Type::STR, RPCArg::Optional::NO, "Address containing repayment tokens. If \"from\" value is: \"*\" (star), it's means auto-selection accounts from wallet."}, {"amounts", RPCArg::Type::STR, RPCArg::Optional::NO, "Amount in amount@token format."}, }, }, @@ -1169,16 +1169,41 @@ UniValue paybackloan(const JSONRPCRequest& request) { else throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"vaultId\" must be non-null"); - if (!metaObj["from"].isNull()) - loanPayback.from = DecodeScript(metaObj["from"].getValStr()); - else - throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"from\" must not be null"); - if (!metaObj["amounts"].isNull()) loanPayback.amounts = DecodeAmounts(pwallet->chain(), metaObj["amounts"], ""); else throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"amounts\" must not be null"); + if (!metaObj["from"].isNull()) { + auto fromStr = metaObj["from"].getValStr(); + if (fromStr == "*") { + auto selectedAccounts = SelectAccountsByTargetBalances(GetAllMineAccounts(pwallet), loanPayback.amounts, SelectionPie); + + for (auto& account : selectedAccounts) { + auto it = loanPayback.amounts.balances.begin(); + while (it != loanPayback.amounts.balances.end()) { + if (account.second.balances[it->first] < it->second) { + break; + } + it++; + } + if (it == loanPayback.amounts.balances.end()) { + loanPayback.from = account.first; + break; + } + } + + if (loanPayback.from.empty()) { + throw JSONRPCError(RPC_INVALID_REQUEST, + "Not enough tokens on account, call sendtokenstoaddress to increase it.\n"); + } + } else { + loanPayback.from = DecodeScript(fromStr); + } + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"from\" must not be null"); + } + if (!::IsMine(*pwallet, loanPayback.from)) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Address (%s) is not owned by the wallet", metaObj["from"].getValStr())); diff --git a/src/masternodes/rpc_vault.cpp b/src/masternodes/rpc_vault.cpp index ba3772c5e77..c835819cedc 100644 --- a/src/masternodes/rpc_vault.cpp +++ b/src/masternodes/rpc_vault.cpp @@ -866,7 +866,7 @@ UniValue placeauctionbid(const JSONRPCRequest& request) { { {"vaultId", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Vault id"}, {"index", RPCArg::Type::NUM, RPCArg::Optional::NO, "Auction index"}, - {"from", RPCArg::Type::STR, RPCArg::Optional::NO, "Address to get tokens"}, + {"from", RPCArg::Type::STR, RPCArg::Optional::NO, "Address to get tokens. If \"from\" value is: \"*\" (star), it's means auto-selection accounts from wallet."}, {"amount", RPCArg::Type::STR, RPCArg::Optional::NO, "Amount of amount@symbol format"}, {"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array of json objects", { @@ -902,9 +902,28 @@ UniValue placeauctionbid(const JSONRPCRequest& request) { // decode vaultId CVaultId vaultId = ParseHashV(request.params[0], "vaultId"); uint32_t index = request.params[1].get_int(); - auto from = DecodeScript(request.params[2].get_str()); CTokenAmount amount = DecodeAmount(pwallet->chain(), request.params[3].get_str(), "amount"); + CScript from = {}; + auto fromStr = request.params[2].get_str(); + if (fromStr == "*") { + auto selectedAccounts = SelectAccountsByTargetBalances(GetAllMineAccounts(pwallet), CBalances{TAmounts{{amount.nTokenId, amount.nValue}}}, SelectionPie); + + for (auto& account : selectedAccounts) { + if (account.second.balances[amount.nTokenId] >= amount.nValue) { + from = account.first; + break; + } + } + + if (from.empty()) { + throw JSONRPCError(RPC_INVALID_REQUEST, + "Not enough tokens on account, call sendtokenstoaddress to increase it.\n"); + } + } else { + from = DecodeScript(fromStr); + } + CAuctionBidMessage msg{vaultId, index, from, amount}; CDataStream markedMetadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); markedMetadata << static_cast(CustomTxType::AuctionBid) diff --git a/test/functional/feature_loan.py b/test/functional/feature_loan.py index 7d20bdda2d5..9aef8645dc1 100755 --- a/test/functional/feature_loan.py +++ b/test/functional/feature_loan.py @@ -19,8 +19,8 @@ def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1'], - ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1'] + ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-bayfrontgardensheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1'], + ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-bayfrontgardensheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1'] ] def run_test(self): @@ -233,11 +233,56 @@ def run_test(self): assert_equal(vault1['state'], "active") assert_equal(accountBal, ['1600.00000000@DFI', '1600.00000000@BTC', '226.00000000@TSLA']) + self.nodes[0].deposittovault(vaultId1, account, '600@DFI') self.nodes[0].generate(1) vault1 = self.nodes[0].getvault(vaultId1) assert_equal(vault1['collateralAmounts'], ['600.00000000@DFI']) + # Trigger liquidation updating price in oracle + oracle1_prices = [{"currency": "USD", "tokenAmount": "2@TSLA"}] + oracle2_prices = [{"currency": "USD", "tokenAmount": "3@TSLA"}] + timestamp = calendar.timegm(time.gmtime()) + self.nodes[0].setoracledata(oracle_id1, timestamp, oracle1_prices) + self.nodes[0].setoracledata(oracle_id2, timestamp, oracle2_prices) + self.nodes[0].generate(36) + + # take loan + self.nodes[0].takeloan({ + 'vaultId': vaultId1, + 'amounts': "1000@TSLA" + }) + self.nodes[0].generate(1) + + # Trigger liquidation updating price in oracle + oracle1_prices = [{"currency": "USD", "tokenAmount": "200@TSLA"}] + oracle2_prices = [{"currency": "USD", "tokenAmount": "300@TSLA"}] + timestamp = calendar.timegm(time.gmtime()) + self.nodes[0].setoracledata(oracle_id1, timestamp, oracle1_prices) + self.nodes[0].setoracledata(oracle_id2, timestamp, oracle2_prices) + self.nodes[0].generate(36) + + # Not enought tokens on account + try: + self.nodes[0].placeauctionbid(vaultId1, 0, "*", "1600@TSLA") + except JSONRPCException as e: + errorString = e.error['message'] + assert("Not enough tokens on account, call accounttoaccount to increase it." in errorString) + + newAddress = self.nodes[0].getnewaddress("") + self.nodes[0].sendtokenstoaddress({}, {newAddress: "1600@TSLA"}) # newAddress has now the highest amount of TSLA token + self.nodes[0].generate(1) + + self.nodes[0].placeauctionbid(vaultId1, 0, "*", "1600@TSLA") # Autoselect address with highest amount of TSLA token + self.nodes[0].generate(40) # let auction end + + auction = self.nodes[0].listauctionhistory(newAddress)[0] + assert_equal(auction['winner'], newAddress) + assert_equal(auction['blockHeight'], 600) + assert_equal(auction['vaultId'], vaultId1) + assert_equal(auction['batchIndex'], 0) + assert_equal(auction['auctionBid'], "1600.00000000@TSLA") + if __name__ == '__main__': LoanTest().main() diff --git a/test/functional/feature_loan_basics.py b/test/functional/feature_loan_basics.py index 7b9752b39c5..c9615754682 100755 --- a/test/functional/feature_loan_basics.py +++ b/test/functional/feature_loan_basics.py @@ -19,8 +19,8 @@ def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-fortcanningheight=50', '-eunosheight=50', '-txindex=1'], - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-fortcanningheight=50', '-eunosheight=50', '-txindex=1']] + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-txindex=1'], + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-txindex=1']] def run_test(self): assert_equal(len(self.nodes[0].listtokens()), 1) # only one token == DFI @@ -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.00186824')) + assert_equal(self.nodes[0].getburninfo()['paybackburn'], Decimal('0.00186822')) assert_equal(sorted(vaultInfo['loanAmounts']), sorted(['0.50000057@' + symbolTSLA, '1.00000133@' + symbolGOOGL])) try: @@ -409,7 +409,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.00443691')) + assert_greater_than_or_equal(self.nodes[0].getburninfo()['paybackburn'], Decimal('0.00443685')) for interest in self.nodes[0].getinterest('LOAN150'): if interest['token'] == symbolTSLA: @@ -458,5 +458,63 @@ def run_test(self): assert_equal(len(vaultInfo['batches']), 1) assert_equal(vaultInfo['batches'][0]['collaterals'], ['400.00000000@DFI']) + address = self.nodes[0].getnewaddress() + self.nodes[0].utxostoaccount({address: "100@" + symbolDFI}) + self.nodes[0].generate(1) + + vaultId3 = self.nodes[0].createvault(address) + self.nodes[0].generate(1) + + self.nodes[0].deposittovault(vaultId3, address, "100@DFI") + self.nodes[0].generate(1) + vault = self.nodes[0].getvault(vaultId3) + print("vault", vault) + # take loan + self.nodes[0].takeloan({ + 'vaultId': vaultId3, + 'amounts': ["10@TSLA", "10@GOOGL"] + }) + self.nodes[0].generate(1) + + address2 = self.nodes[0].getnewaddress() + self.nodes[0].sendtokenstoaddress({}, {address2:["5@TSLA", "5@GOOGL"]}) # split into two address + self.nodes[0].generate(1) + + listaccounts = self.nodes[0].listaccounts({}, False, False, True) + print("listaccounts", listaccounts) + + try: + self.nodes[0].paybackloan({ + 'vaultId': vaultId3, + 'from': "*", + 'amounts': ["10@" + symbolTSLA, "10@" + symbolGOOGL] + }) + except JSONRPCException as e: + errorString = e.error['message'] + assert("Not enough tokens on account, call sendtokenstoaddress to increase it." in errorString) + + self.nodes[0].paybackloan({ + 'vaultId': vaultId3, + 'from': "*", + 'amounts': "5@" + symbolTSLA + }) + self.nodes[0].generate(1) + + vault = self.nodes[0].getvault(vaultId3) + assert_equal(sorted(vault['loanAmounts']), sorted(['5.00002853@' + symbolTSLA, '10.00003993@' + symbolGOOGL])) + + self.nodes[0].sendtokenstoaddress({}, {address2:["5@" + symbolTSLA, "10@" + symbolGOOGL]}) + self.nodes[0].generate(1) + + self.nodes[0].paybackloan({ + 'vaultId': vaultId3, + 'from': "*", + 'amounts': ["5@" + symbolTSLA, "10@" + symbolGOOGL] + }) + self.nodes[0].generate(1) + + vault = self.nodes[0].getvault(vaultId3) + assert_equal(sorted(vault['loanAmounts']), sorted(['0.00003425@' + symbolTSLA, '0.00005324@' + symbolGOOGL])) + if __name__ == '__main__': LoanTakeLoanTest().main() From 0535a08a679dc76ec42f4294dfd76bedb7a9a43e Mon Sep 17 00:00:00 2001 From: jouzo Date: Fri, 26 Nov 2021 16:22:25 +0100 Subject: [PATCH 2/5] Reduce complexity by throwing first if isNull --- src/masternodes/rpc_loan.cpp | 45 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/masternodes/rpc_loan.cpp b/src/masternodes/rpc_loan.cpp index 3ab51623669..628f031e435 100644 --- a/src/masternodes/rpc_loan.cpp +++ b/src/masternodes/rpc_loan.cpp @@ -1174,35 +1174,36 @@ UniValue paybackloan(const JSONRPCRequest& request) { else throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"amounts\" must not be null"); - if (!metaObj["from"].isNull()) { - auto fromStr = metaObj["from"].getValStr(); - if (fromStr == "*") { - auto selectedAccounts = SelectAccountsByTargetBalances(GetAllMineAccounts(pwallet), loanPayback.amounts, SelectionPie); - - for (auto& account : selectedAccounts) { - auto it = loanPayback.amounts.balances.begin(); - while (it != loanPayback.amounts.balances.end()) { - if (account.second.balances[it->first] < it->second) { - break; - } - it++; - } - if (it == loanPayback.amounts.balances.end()) { - loanPayback.from = account.first; + if (metaObj["from"].isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"from\" must not be null"); + } + + auto fromStr = metaObj["from"].getValStr(); + if (fromStr == "*") { + auto selectedAccounts = SelectAccountsByTargetBalances(GetAllMineAccounts(pwallet), loanPayback.amounts, SelectionPie); + + for (auto& account : selectedAccounts) { + auto it = loanPayback.amounts.balances.begin(); + while (it != loanPayback.amounts.balances.end()) { + if (account.second.balances[it->first] < it->second) { break; } + it++; } - - if (loanPayback.from.empty()) { - throw JSONRPCError(RPC_INVALID_REQUEST, - "Not enough tokens on account, call sendtokenstoaddress to increase it.\n"); + if (it == loanPayback.amounts.balances.end()) { + loanPayback.from = account.first; + break; } - } else { - loanPayback.from = DecodeScript(fromStr); + } + + if (loanPayback.from.empty()) { + throw JSONRPCError(RPC_INVALID_REQUEST, + "Not enough tokens on account, call sendtokenstoaddress to increase it.\n"); } } else { - throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"from\" must not be null"); + loanPayback.from = DecodeScript(fromStr); } +} if (!::IsMine(*pwallet, loanPayback.from)) throw JSONRPCError(RPC_INVALID_PARAMETER, From b5bbba23e30722d49b756791006aca13754ee6c4 Mon Sep 17 00:00:00 2001 From: jouzo Date: Fri, 26 Nov 2021 23:17:03 +0100 Subject: [PATCH 3/5] Remove dangling brace --- src/masternodes/rpc_loan.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/masternodes/rpc_loan.cpp b/src/masternodes/rpc_loan.cpp index 628f031e435..d64eb6e3ed5 100644 --- a/src/masternodes/rpc_loan.cpp +++ b/src/masternodes/rpc_loan.cpp @@ -1203,7 +1203,6 @@ UniValue paybackloan(const JSONRPCRequest& request) { } else { loanPayback.from = DecodeScript(fromStr); } -} if (!::IsMine(*pwallet, loanPayback.from)) throw JSONRPCError(RPC_INVALID_PARAMETER, From dd7cad4bfb37f87e2e7882fc7495912a3f16ff7e Mon Sep 17 00:00:00 2001 From: jouzo Date: Sat, 27 Nov 2021 10:15:40 +0100 Subject: [PATCH 4/5] Fix test assertion --- test/functional/feature_loan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/feature_loan.py b/test/functional/feature_loan.py index 9aef8645dc1..d831dcdd410 100755 --- a/test/functional/feature_loan.py +++ b/test/functional/feature_loan.py @@ -267,7 +267,7 @@ def run_test(self): self.nodes[0].placeauctionbid(vaultId1, 0, "*", "1600@TSLA") except JSONRPCException as e: errorString = e.error['message'] - assert("Not enough tokens on account, call accounttoaccount to increase it." in errorString) + assert("Not enough tokens on account, call sendtokenstoaddress to increase it." in errorString) newAddress = self.nodes[0].getnewaddress("") self.nodes[0].sendtokenstoaddress({}, {newAddress: "1600@TSLA"}) # newAddress has now the highest amount of TSLA token From 98ac64f5b7454d893c961b4a68194a4541da73a1 Mon Sep 17 00:00:00 2001 From: jouzo Date: Mon, 29 Nov 2021 13:13:44 +0100 Subject: [PATCH 5/5] Remove print in tests --- test/functional/feature_loan.py | 3 ++- test/functional/feature_loan_basics.py | 7 +------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/test/functional/feature_loan.py b/test/functional/feature_loan.py index d831dcdd410..ee97632ff01 100755 --- a/test/functional/feature_loan.py +++ b/test/functional/feature_loan.py @@ -105,6 +105,7 @@ def run_test(self): 'mintable': True, 'interest': 1}) self.nodes[0].generate(6) + # take loan self.nodes[0].takeloan({ 'vaultId': vaultId1, @@ -254,7 +255,7 @@ def run_test(self): }) self.nodes[0].generate(1) - # Trigger liquidation updating price in oracle + # Reset price in oracle oracle1_prices = [{"currency": "USD", "tokenAmount": "200@TSLA"}] oracle2_prices = [{"currency": "USD", "tokenAmount": "300@TSLA"}] timestamp = calendar.timegm(time.gmtime()) diff --git a/test/functional/feature_loan_basics.py b/test/functional/feature_loan_basics.py index c9615754682..92d2f61ec09 100755 --- a/test/functional/feature_loan_basics.py +++ b/test/functional/feature_loan_basics.py @@ -402,7 +402,6 @@ def run_test(self): 'from': account0, 'amounts': vaultInfo['loanAmounts']}) - self.nodes[0].generate(1) self.sync_blocks() @@ -467,8 +466,7 @@ def run_test(self): self.nodes[0].deposittovault(vaultId3, address, "100@DFI") self.nodes[0].generate(1) - vault = self.nodes[0].getvault(vaultId3) - print("vault", vault) + # take loan self.nodes[0].takeloan({ 'vaultId': vaultId3, @@ -480,9 +478,6 @@ def run_test(self): self.nodes[0].sendtokenstoaddress({}, {address2:["5@TSLA", "5@GOOGL"]}) # split into two address self.nodes[0].generate(1) - listaccounts = self.nodes[0].listaccounts({}, False, False, True) - print("listaccounts", listaccounts) - try: self.nodes[0].paybackloan({ 'vaultId': vaultId3,