Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds estimatevault RPC #933

Merged
merged 4 commits into from
Dec 2, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 25 additions & 23 deletions src/masternodes/masternodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,20 @@ CAmount CCollateralLoans::precisionRatio() const
return ratio > maxRatio / precision ? -COIN : CAmount(ratio * precision);
}

ResVal<CAmount> CCustomCSView::GetAmountInCurrency(CAmount amount, CTokenCurrencyPair priceFeedId, bool useNextPrice, bool requireLivePrice)
{
auto priceResult = GetValidatedIntervalPrice(priceFeedId, useNextPrice, requireLivePrice);
if (!priceResult)
return std::move(priceResult);

auto price = priceResult.val.get();
auto amountInCurrency = MultiplyAmounts(price, amount);
if (price > COIN && amountInCurrency < amount)
return Res::Err("Value/price too high (%s/%s)", GetDecimaleString(amount), GetDecimaleString(price));

return ResVal<CAmount>(amountInCurrency, Res::Ok());
}

ResVal<CCollateralLoans> CCustomCSView::GetLoanCollaterals(CVaultId const& vaultId, CBalances const& collaterals, uint32_t height,
int64_t blockTime, bool useNextPrice, bool requireLivePrice)
{
Expand Down Expand Up @@ -992,22 +1006,15 @@ Res CCustomCSView::PopulateLoansData(CCollateralLoans& result, CVaultId const& v
if (rate->height > height)
return Res::Err("Trying to read loans in the past");

auto priceResult = GetValidatedIntervalPrice(token->fixedIntervalPriceId, useNextPrice, requireLivePrice);
if (!priceResult)
return std::move(priceResult);

auto price = priceResult.val.get();

LogPrint(BCLog::LOAN,"\t\t%s()->for_loans->%s->", __func__, token->symbol); /* Continued */

auto value = loanTokenAmount + TotalInterest(*rate, height);
auto amountInCurrency = MultiplyAmounts(price, value);

if (price > COIN && amountInCurrency < value)
return Res::Err("Value/price too high (%s/%s)", GetDecimaleString(value), GetDecimaleString(price));
auto totalAmount = loanTokenAmount + TotalInterest(*rate, height);
auto amountInCurrency = GetAmountInCurrency(totalAmount, token->fixedIntervalPriceId, useNextPrice, requireLivePrice);
if (!amountInCurrency)
return std::move(amountInCurrency);

auto prevLoans = result.totalLoans;
result.totalLoans += amountInCurrency;
result.totalLoans += *amountInCurrency.val;

if (prevLoans > result.totalLoans)
return Res::Err("Exceeded maximum loans");
Expand All @@ -1028,20 +1035,15 @@ Res CCustomCSView::PopulateCollateralData(CCollateralLoans& result, CVaultId con
if (!token)
return Res::Err("Collateral token with id (%s) does not exist!", tokenId.ToString());

auto priceResult = GetValidatedIntervalPrice(token->fixedIntervalPriceId, useNextPrice, requireLivePrice);
if (!priceResult)
return std::move(priceResult);

auto price = priceResult.val.get();
auto amountInCurrency = GetAmountInCurrency(tokenAmount, token->fixedIntervalPriceId, useNextPrice, requireLivePrice);
if (!amountInCurrency)
return std::move(amountInCurrency);

auto amountInCurrency = MultiplyAmounts(price, tokenAmount);
if (price > COIN && amountInCurrency < tokenAmount)
return Res::Err("Value/price too high (%s/%s)", GetDecimaleString(tokenAmount), GetDecimaleString(price));

amountInCurrency = MultiplyAmounts(token->factor, amountInCurrency);
auto amountFactor = MultiplyAmounts(token->factor, *amountInCurrency.val);

auto prevCollaterals = result.totalCollaterals;
result.totalCollaterals += amountInCurrency;
result.totalCollaterals += amountFactor;

if (prevCollaterals > result.totalCollaterals)
return Res::Err("Exceeded maximum collateral");

Expand Down
2 changes: 2 additions & 0 deletions src/masternodes/masternodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,8 @@ class CCustomCSView

bool CalculateOwnerRewards(CScript const & owner, uint32_t height);

ResVal<CAmount> GetAmountInCurrency(CAmount amount, CTokenCurrencyPair priceFeedId, bool useNextPrice = false, bool requireLivePrice = true);

ResVal<CCollateralLoans> GetLoanCollaterals(CVaultId const & vaultId, CBalances const & collaterals, uint32_t height, int64_t blockTime, bool useNextPrice = false, bool requireLivePrice = true);

void SetDbVersion(int version);
Expand Down
68 changes: 68 additions & 0 deletions src/masternodes/rpc_vault.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1491,6 +1491,73 @@ UniValue listvaulthistory(const JSONRPCRequest& request) {
return slice;
}

UniValue estimatevault(const JSONRPCRequest& request) {
auto pwallet = GetWallet(request);

RPCHelpMan{"estimatevault",
"Returns estimated vault for given collateral and loan amounts.\n",
{
{"collateralAmounts", RPCArg::Type::STR, RPCArg::Optional::NO,
"Collateral amounts as json string, or array. Example: '[ \"amount@token\" ]'"
},
{"loanAmounts", RPCArg::Type::STR, RPCArg::Optional::NO,
"Loan amounts as json string, or array. Example: '[ \"amount@token\" ]'"
},
},
RPCResult{
"{\n"
" \"collateralValue\" : n.nnnnnnnn, (amount) The total collateral value in USD\n"
" \"loanValue\" : n.nnnnnnnn, (amount) The total loan value in USD\n"
" \"informativeRatio\" : n.nnnnnnnn, (amount) Informative ratio with 8 digit precision\n"
" \"collateralRatio\" : n, (uint) Ratio as unsigned int\n"
"}\n"
},
RPCExamples{
HelpExampleCli("estimatevault", R"('["1000.00000000@DFI"]' '["0.65999990@GOOGL"]')") +
HelpExampleRpc("estimatevault", R"(["1000.00000000@DFI"], ["0.65999990@GOOGL"])")
},
}.Check(request);

CBalances collateralBalances = DecodeAmounts(pwallet->chain(), request.params[0], "");
CBalances loanBalances = DecodeAmounts(pwallet->chain(), request.params[1], "");

LOCK(cs_main);
auto height = (uint32_t) ::ChainActive().Height();

CCollateralLoans result{};

for (const auto& collateral : collateralBalances.balances) {
auto collateralToken = pcustomcsview->HasLoanCollateralToken({collateral.first, height});
if (!collateralToken || !collateralToken->factor) {
throw JSONRPCError(RPC_DATABASE_ERROR, strprintf("Token with id (%s) is not a valid collateral!", collateral.first.ToString()));
}

auto amountInCurrency = pcustomcsview->GetAmountInCurrency(collateral.second, collateralToken->fixedIntervalPriceId);
if (!amountInCurrency) {
throw JSONRPCError(RPC_DATABASE_ERROR, amountInCurrency.msg);
}
result.totalCollaterals += MultiplyAmounts(collateralToken->factor, *amountInCurrency.val);;
}

for (const auto& loan : loanBalances.balances) {
auto loanToken = pcustomcsview->GetLoanTokenByID(loan.first);
if (!loanToken) throw JSONRPCError(RPC_INVALID_PARAMETER, "Token with id (" + loan.first.ToString() + ") is not a loan token!");

auto amountInCurrency = pcustomcsview->GetAmountInCurrency(loan.second, loanToken->fixedIntervalPriceId);
if (!amountInCurrency) {
throw JSONRPCError(RPC_DATABASE_ERROR, amountInCurrency.msg);
}
result.totalLoans += *amountInCurrency.val;
}

UniValue ret(UniValue::VOBJ);
ret.pushKV("collateralValue", ValueFromUint(result.totalCollaterals));
ret.pushKV("loanValue", ValueFromUint(result.totalLoans));
ret.pushKV("informativeRatio", ValueFromAmount(result.precisionRatio()));
ret.pushKV("collateralRatio", int(result.ratio()));
return ret;
}

static const CRPCCommand commands[] =
{
// category name actor (function) params
Expand All @@ -1506,6 +1573,7 @@ static const CRPCCommand commands[] =
{"vault", "placeauctionbid", &placeauctionbid, {"id", "index", "from", "amount", "inputs"}},
{"vault", "listauctions", &listauctions, {"pagination"}},
{"vault", "listauctionhistory", &listauctionhistory, {"owner", "pagination"}},
{"vault", "estimatevault", &estimatevault, {"collateralAmounts", "loanAmounts"}},
};

void RegisterVaultRPCCommands(CRPCTable& tableRPC) {
Expand Down
2 changes: 2 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "listvaults", 1, "pagination" },
{ "listauctions", 0, "pagination" },
{ "listauctionhistory", 1, "pagination" },
{ "estimatevault", 0, "collateralAmounts" },
{ "estimatevault", 1, "loanAmounts" },

{ "spv_sendrawtx", 0, "rawtx" },
{ "spv_createanchor", 0, "inputs" },
Expand Down
42 changes: 39 additions & 3 deletions test/functional/feature_loan_vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# 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 Scheme."""
"""Test vault."""

from decimal import Decimal
from test_framework.test_framework import DefiTestFramework
Expand All @@ -12,6 +12,7 @@
from test_framework.util import assert_equal, assert_raises_rpc_error
import calendar
import time

class VaultTest (DefiTestFramework):
def set_test_params(self):
self.num_nodes = 2
Expand Down Expand Up @@ -247,7 +248,7 @@ def run_test(self):

self.nodes[0].setcollateraltoken({
'token': idBTC,
'factor': 1,
'factor': 0.8,
'fixedIntervalPriceId': "BTC/USD"})

self.nodes[0].generate(7)
Expand Down Expand Up @@ -336,7 +337,7 @@ def run_test(self):

vault1 = self.nodes[0].getvault(vaultId1)
assert_equal(vault1['loanAmounts'], ['0.50000047@TSLA'])
assert_equal(vault1['collateralValue'], Decimal('2.00000000'))
assert_equal(vault1['collateralValue'], Decimal('1.800000000'))
assert_equal(vault1['loanValue'],Decimal('0.50000047'))
assert_equal(vault1['interestValue'],Decimal('0.00000047'))
assert_equal(vault1['interestAmounts'],['0.00000047@TSLA'])
Expand Down Expand Up @@ -418,6 +419,41 @@ def run_test(self):
# collaterals 2.5 + 0.5 fee
assert_equal(self.nodes[0].getaccount(ownerAddress2)[0], '3.00000000@DFI')

# Invalid loan token
try:
estimatevault = self.nodes[0].estimatevault('3.00000000@DFI', '3.00000000@TSLAA')
except JSONRPCException as e:
errorString = e.error['message']
print("errorString", errorString)
assert("Invalid Defi token: TSLAA" in errorString)
# Invalid collateral token
try:
estimatevault = self.nodes[0].estimatevault('3.00000000@DFII', '3.00000000@TSLA')
except JSONRPCException as e:
errorString = e.error['message']
print("errorString", errorString)
assert("Invalid Defi token: DFII" in errorString)
# Token not set as a collateral
try:
estimatevault = self.nodes[0].estimatevault('3.00000000@TSLA', '3.00000000@TSLA')
except JSONRPCException as e:
errorString = e.error['message']
print("errorString", errorString)
assert("Token with id (2) is not a valid collateral!" in errorString)
# Token not set as loan token
try:
estimatevault = self.nodes[0].estimatevault('3.00000000@DFI', '3.00000000@DFI')
except JSONRPCException as e:
errorString = e.error['message']
print("errorString", errorString)
assert("Token with id (0) is not a loan token!" in errorString)

vault = self.nodes[0].getvault(vaultId2)
estimatevault = self.nodes[0].estimatevault(vault["collateralAmounts"], vault["loanAmounts"])
assert_equal(estimatevault["collateralValue"], vault["collateralValue"])
assert_equal(estimatevault["loanValue"], vault["loanValue"])
assert_equal(estimatevault["informativeRatio"], vault["informativeRatio"])
assert_equal(estimatevault["collateralRatio"], vault["collateralRatio"])

if __name__ == '__main__':
VaultTest().main()