diff --git a/src/masternodes/loan.cpp b/src/masternodes/loan.cpp index 7fac131f34..7a63a4620d 100644 --- a/src/masternodes/loan.cpp +++ b/src/masternodes/loan.cpp @@ -74,9 +74,6 @@ Res CLoanView::SetLoanToken(CLoanSetLoanTokenImpl const & loanToken, DCT_ID cons Res CLoanView::UpdateLoanToken(CLoanSetLoanTokenImpl const & loanToken, DCT_ID const & id) { - if (loanToken.interest < 0) - return Res::Err("interest rate cannot be less than 0!"); - WriteBy(id, loanToken); return Res::Ok(); diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index da816359ef..52c08c54ba 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -537,10 +537,7 @@ class CCustomMetadataParseVisitor : public boost::static_visitor Res operator()(CLoanUpdateLoanTokenMessage& obj) const { auto res = isPostFortCanningFork(); - if (!res) - return res; - res = isPostFortCanningCrunchFork(); - return res ? Res::Err("called after FortCanningCrunch height") : serialize(obj); + return !res ? res : serialize(obj); } Res operator()(CLoanSchemeMessage& obj) const { @@ -2458,7 +2455,16 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor if (!HasFoundationAuth()) return Res::Err("tx not from foundation member!"); - auto loanToken = mnview.GetLoanToken(obj.tokenTx); + if (obj.interest < 0) + return Res::Err("interest rate cannot be less than 0!"); + + auto pair = mnview.GetTokenByCreationTx(obj.tokenTx); + if (!pair) + return Res::Err("Loan token (%s) does not exist!", obj.tokenTx.GetHex()); + + auto loanToken = height >= static_cast(consensus.FortCanningCrunchHeight) ? + mnview.GetLoanTokenByID(pair->first) : mnview.GetLoanToken(obj.tokenTx); + if (!loanToken) return Res::Err("Loan token (%s) does not exist!", obj.tokenTx.GetHex()); @@ -2468,23 +2474,12 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor if (obj.interest != loanToken->interest) loanToken->interest = obj.interest; - auto pair = mnview.GetTokenByCreationTx(obj.tokenTx); - if (!pair) - return Res::Err("Loan token (%s) does not exist!", obj.tokenTx.GetHex()); - if (obj.symbol != pair->second.symbol) pair->second.symbol = trim_ws(obj.symbol).substr(0, CToken::MAX_TOKEN_SYMBOL_LENGTH); if (obj.name != pair->second.name) pair->second.name = trim_ws(obj.name).substr(0, CToken::MAX_TOKEN_NAME_LENGTH); - if (obj.fixedIntervalPriceId != loanToken->fixedIntervalPriceId) { - if (!OraclePriceFeed(mnview, obj.fixedIntervalPriceId)) - return Res::Err("Price feed %s/%s does not belong to any oracle", obj.fixedIntervalPriceId.first, obj.fixedIntervalPriceId.second); - - loanToken->fixedIntervalPriceId = obj.fixedIntervalPriceId; - } - if (obj.mintable != (pair->second.flags & (uint8_t)CToken::TokenFlags::Mintable)) pair->second.flags ^= (uint8_t)CToken::TokenFlags::Mintable; @@ -2492,6 +2487,39 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor if (!res) return res; + if (height >= static_cast(consensus.FortCanningCrunchHeight)) + { + const auto& id = pair->first.v; + + auto attributes = mnview.GetAttributes(); + attributes->time = time; + + CDataStructureV0 mintEnabled{AttributeTypes::Token, id, TokenKeys::LoanMintingEnabled}; + CDataStructureV0 mintInterest{AttributeTypes::Token, id, TokenKeys::LoanMintingInterest}; + CDataStructureV0 pairKey{AttributeTypes::Token, id, TokenKeys::FixedIntervalPriceId}; + + attributes->SetValue(mintEnabled, obj.mintable); + attributes->SetValue(mintInterest, obj.interest); + attributes->SetValue(pairKey, obj.fixedIntervalPriceId); + + res = attributes->Validate(mnview); + if (!res) + return res; + + res = attributes->Apply(mnview, height); + if (!res) + return res; + + return mnview.SetVariable(*attributes); + } + + if (obj.fixedIntervalPriceId != loanToken->fixedIntervalPriceId) { + if (!OraclePriceFeed(mnview, obj.fixedIntervalPriceId)) + return Res::Err("Price feed %s/%s does not belong to any oracle", obj.fixedIntervalPriceId.first, obj.fixedIntervalPriceId.second); + + loanToken->fixedIntervalPriceId = obj.fixedIntervalPriceId; + } + return mnview.UpdateLoanToken(*loanToken, pair->first); } diff --git a/src/masternodes/rpc_loan.cpp b/src/masternodes/rpc_loan.cpp index 8ddefa9dfd..1aa47df786 100644 --- a/src/masternodes/rpc_loan.cpp +++ b/src/masternodes/rpc_loan.cpp @@ -428,14 +428,14 @@ UniValue updateloantoken(const JSONRPCRequest& request) { UniValue const & txInputs = request.params[2]; boost::optional loanToken; - CTokenImplementation tokenImpl; + boost::optional token; int targetHeight; { LOCK(cs_main); DCT_ID id; - auto token = pcustomcsview->GetTokenGuessId(tokenStr, id); + token = pcustomcsview->GetTokenGuessId(tokenStr, id); if (!token) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Token %s does not exist!", tokenStr)); } @@ -469,7 +469,7 @@ UniValue updateloantoken(const JSONRPCRequest& request) { CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); metadata << static_cast(CustomTxType::UpdateLoanToken) - << static_cast(*loanToken) << loanToken->creationTx; + << static_cast(*loanToken) << token->creationTx; CScript scriptMeta; scriptMeta << OP_RETURN << ToByteVector(metadata); diff --git a/test/functional/feature_loan_setloantoken.py b/test/functional/feature_loan_setloantoken.py index 194e288be5..4c051a7fcf 100755 --- a/test/functional/feature_loan_setloantoken.py +++ b/test/functional/feature_loan_setloantoken.py @@ -52,6 +52,7 @@ def run_test(self): {"currency": "USD", "token": "TSLA"}, {"currency": "USD", "token": "GOOGL"}, {"currency": "USD", "token": "AMZN"}, + {"currency": "USD", "token": "MSFT"}, ] oracle_id1 = self.nodes[0].appointoracle(oracle_address1, price_feeds1, 10) self.nodes[0].generate(1) @@ -82,6 +83,7 @@ def run_test(self): {"currency": "USD", "tokenAmount": "1@TSLA"}, {"currency": "USD", "tokenAmount": "1@GOOGL"}, {"currency": "USD", "tokenAmount": "1@AMZN"}, + {"currency": "USD", "tokenAmount": "1@MSFT"}, ] timestamp = calendar.timegm(time.gmtime()) self.nodes[0].setoracledata(oracle_id1, timestamp, oracle1_prices) @@ -210,5 +212,32 @@ def run_test(self): assert_equal(result['v0/token/5/loan_minting_interest'], '0.01') assert_equal(result['v0/token/5/fixed_interval_price_id'], 'AMZN/USD') + # Update loan token + self.nodes[0].updateloantoken("GOOGL", { + 'symbol': "MSFT", + 'name': "Microsoft", + 'fixedIntervalPriceId': "MSFT/USD", + 'mintable': False, + 'interest': 0.05}) + self.nodes[0].generate(1) + + # Check tokens + result = self.nodes[0].gettoken("MSFT")['4'] + assert_equal(result['symbol'], 'MSFT') + assert_equal(result['symbolKey'], 'MSFT') + assert_equal(result['name'], 'Microsoft') + assert_equal(result['mintable'], False) + assert_equal(result['tradeable'], True) + assert_equal(result['isDAT'], True) + assert_equal(result['isLPS'], False) + assert_equal(result['finalized'], False) + assert_equal(result['isLoanToken'], True) + + # Check attributess + result = self.nodes[0].listgovs()[8][0]['ATTRIBUTES'] + assert_equal(result['v0/token/4/loan_minting_enabled'], 'false') + assert_equal(result['v0/token/4/loan_minting_interest'], '0.05') + assert_equal(result['v0/token/4/fixed_interval_price_id'], 'MSFT/USD') + if __name__ == '__main__': LoanSetLoanTokenTest().main() diff --git a/test/functional/feature_loan_vault.py b/test/functional/feature_loan_vault.py index 6de4d45d58..6994ede2c1 100755 --- a/test/functional/feature_loan_vault.py +++ b/test/functional/feature_loan_vault.py @@ -749,14 +749,6 @@ def loan_and_collateral_token_to_govvar(self): # Move to hard fork again self.move_to_gw_fork() - # Try and call disabled RPC calls - assert_raises_rpc_error(-32600, 'called after FortCanningCrunch height', self.nodes[0].updateloantoken, "DUSD", { - 'symbol': "DUSD", - 'name': "DUSD stable token", - 'fixedIntervalPriceId': "DUSD/USD", - 'mintable': True, - 'interest': 1}) - # Test setting collateral token partially self.nodes[0].setgov({"ATTRIBUTES":{f'v0/token/{self.idETH}/fixed_interval_price_id':'ETH/USD', f'v0/token/{self.idETH}/loan_collateral_enabled':'true'}}) self.nodes[0].generate(1)