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

Loan token interest calculation #665

Merged
merged 1 commit into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ DEFI_TESTS =\
test/key_tests.cpp \
test/limitedmap_tests.cpp \
test/liquidity_tests.cpp \
test/loan_tests.cpp \
test/dbwrapper_tests.cpp \
test/validation_tests.cpp \
test/mempool_tests.cpp \
Expand Down
68 changes: 68 additions & 0 deletions src/masternodes/loan.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

#include <chainparams.h>
#include <masternodes/loan.h>

const unsigned char CLoanView::LoanSetCollateralTokenCreationTx ::prefix = 0x10;
Expand All @@ -8,6 +10,7 @@ const unsigned char CLoanView::DelayedLoanSchemeKey ::pref
const unsigned char CLoanView::DestroyLoanSchemeKey ::prefix = 0x15;
const unsigned char CLoanView::LoanSetLoanTokenCreationTx ::prefix = 0x17;
const unsigned char CLoanView::LoanSetLoanTokenKey ::prefix = 0x18;
const unsigned char CLoanView::LoanInterestedRate ::prefix = 0x19;
// Vault
const unsigned char CVaultView::VaultKey ::prefix = 0x16;

Expand Down Expand Up @@ -192,6 +195,71 @@ void CLoanView::EraseDelayedDestroyScheme(const std::string& loanSchemeID)
EraseBy<DestroyLoanSchemeKey>(loanSchemeID);
}

boost::optional<CInterestRate> CLoanView::GetInterestRate(const std::string& loanSchemeID, DCT_ID id)
{
return ReadBy<LoanInterestedRate, CInterestRate>(std::make_pair(loanSchemeID, id));
}

Res CLoanView::StoreInterest(uint32_t height, const std::string& loanSchemeID, DCT_ID id)
{
auto scheme = GetLoanScheme(loanSchemeID);
if (!scheme) {
return Res::Err("No such scheme id %s", loanSchemeID);
}
auto token = GetLoanSetLoanTokenByID(id);
if (!token) {
return Res::Err("No such loan token id %s", id.ToString());
}
CInterestRate rate{};
if (auto storedRate = GetInterestRate(loanSchemeID, id)) {
rate = *storedRate;
}
if (rate.height > height) {
return Res::Err("Cannot store height in the past");
}
if (rate.height) {
rate.interestToHeight += (height - rate.height) * rate.interestPerBlock;
}
rate.count++;
rate.height = height;
int64_t netInterest = scheme->rate + token->interest;
rate.interestPerBlock = netInterest * rate.count / (365 * Params().GetConsensus().blocksPerDay());
WriteBy<LoanInterestedRate>(std::make_pair(loanSchemeID, id), rate);
return Res::Ok();
}

Res CLoanView::EraseInterest(uint32_t height, const std::string& loanSchemeID, DCT_ID id)
{
auto scheme = GetLoanScheme(loanSchemeID);
if (!scheme) {
return Res::Err("No such scheme id %s", loanSchemeID);
}
auto token = GetLoanSetLoanTokenByID(id);
if (!token) {
return Res::Err("No such loan token id %s", id.ToString());
}
CInterestRate rate{};
if (auto storedRate = GetInterestRate(loanSchemeID, id)) {
rate = *storedRate;
}
if (rate.count <= 1) {
EraseBy<LoanInterestedRate>(std::make_pair(loanSchemeID, id));
return Res::Ok();
}
if (rate.height > height) {
return Res::Err("Cannot store height in the past");
}
if (rate.height == 0) {
return Res::Err("Data mismatch height == 0");
}
rate.interestToHeight += (height - rate.height) * rate.interestPerBlock;
rate.count--;
rate.height = height;
int64_t netInterest = scheme->rate + token->interest;
rate.interestPerBlock = netInterest * rate.count / (365 * Params().GetConsensus().blocksPerDay());
WriteBy<LoanInterestedRate>(std::make_pair(loanSchemeID, id), rate);
return Res::Ok();
}

// VAULT

Expand Down
23 changes: 23 additions & 0 deletions src/masternodes/loan.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,24 @@ struct CDestroyLoanSchemeMessage : public CDefaultLoanSchemeMessage
}
};

struct CInterestRate {
uint32_t count = 0;
uint32_t height = 0;
CAmount interestToHeight = 0;
CAmount interestPerBlock = 0;

ADD_SERIALIZE_METHODS;

template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action)
{
READWRITE(count);
READWRITE(height);
READWRITE(interestToHeight);
READWRITE(interestPerBlock);
}
};

// use vault's creation tx for ID
using CVaultId = uint256;
struct CVaultMessage {
Expand Down Expand Up @@ -226,6 +244,10 @@ class CLoanView : public virtual CStorageView {
void ForEachDelayedLoanScheme(std::function<bool (const std::pair<std::string, uint64_t>&, const CLoanSchemeMessage&)> callback);
void ForEachDelayedDestroyScheme(std::function<bool (const std::string&, const uint64_t&)> callback);

boost::optional<CInterestRate> GetInterestRate(const std::string& loanSchemeID, DCT_ID id);
Res StoreInterest(uint32_t height, const std::string& loanSchemeID, DCT_ID id);
Res EraseInterest(uint32_t height, const std::string& loanSchemeID, DCT_ID id);

struct LoanSetCollateralTokenCreationTx { static const unsigned char prefix; };
struct LoanSetCollateralTokenKey { static const unsigned char prefix; };
struct LoanSetLoanTokenCreationTx { static const unsigned char prefix; };
Expand All @@ -234,6 +256,7 @@ class CLoanView : public virtual CStorageView {
struct DefaultLoanSchemeKey { static const unsigned char prefix; };
struct DelayedLoanSchemeKey { static const unsigned char prefix; };
struct DestroyLoanSchemeKey { static const unsigned char prefix; };
struct LoanInterestedRate { static const unsigned char prefix; };
};

class CVaultView : public virtual CStorageView
Expand Down
99 changes: 99 additions & 0 deletions src/test/loan_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include <chainparams.h>
#include <masternodes/loan.h>
#include <masternodes/masternodes.h>
#include <validation.h>

#include <test/setup_common.h>

#include <boost/test/unit_test.hpp>


inline uint256 NextTx()
{
static int txs_counter = 1;

std::stringstream stream;
stream << std::hex << txs_counter++;
return uint256S(stream.str() );
}

DCT_ID CreateLoanToken(CCustomCSView &mnview, CAmount interest)
{
CTokenImplementation token;
CLoanSetLoanTokenImplementation loanToken;
loanToken.interest = interest;
loanToken.creationTx = token.creationTx = NextTx();
token.flags = (uint8_t)CToken::TokenFlags::Default;
token.flags |= (uint8_t)CToken::TokenFlags::LoanToken | (uint8_t)CToken::TokenFlags::DAT;

token.symbol = "TST";
token.name = "Test";

auto res = mnview.CreateToken(token, false);
if (!res.ok) printf("%s\n", res.msg.c_str());
BOOST_REQUIRE(res.ok);
auto id = *res.val;
mnview.LoanSetLoanToken(loanToken, id);
return id;
}

BOOST_FIXTURE_TEST_SUITE(loan_tests, TestingSetup)

BOOST_AUTO_TEST_CASE(loan_iterest_rate)
{
CCustomCSView mnview(*pcustomcsview);

const std::string id("sch1");
CLoanSchemeMessage msg;
msg.ratio = 150;
msg.rate = 2 * COIN;
msg.identifier = id;
mnview.StoreLoanScheme(msg);

const CAmount tokenInterest = 5 * COIN;
auto token_id = CreateLoanToken(mnview, tokenInterest);
auto scheme = mnview.GetLoanScheme(id);
BOOST_REQUIRE(scheme);
BOOST_CHECK_EQUAL(scheme->ratio, 150);
BOOST_CHECK_EQUAL(scheme->rate, 2 * COIN);

mnview.StoreInterest(1, id, token_id);
mnview.StoreInterest(1, id, token_id);
mnview.StoreInterest(1, id, token_id);

auto rate = mnview.GetInterestRate(id, token_id);
BOOST_REQUIRE(rate);
BOOST_CHECK_EQUAL(rate->count, 3);
BOOST_CHECK_EQUAL(rate->height, 1);
auto netInterest = scheme->rate + tokenInterest;
BOOST_CHECK_EQUAL(rate->interestPerBlock, netInterest * rate->count / (365 * Params().GetConsensus().blocksPerDay()));

auto interestPerBlock = rate->interestPerBlock + rate->interestToHeight;
mnview.StoreInterest(5, id, token_id);
mnview.StoreInterest(5, id, token_id);

rate = mnview.GetInterestRate(id, token_id);
BOOST_REQUIRE(rate);
BOOST_CHECK_EQUAL(rate->count, 5);
BOOST_CHECK_EQUAL(rate->height, 5);
BOOST_CHECK_EQUAL(rate->interestToHeight, 4 * interestPerBlock);
BOOST_CHECK_EQUAL(rate->interestPerBlock, netInterest * rate->count / (365 * Params().GetConsensus().blocksPerDay()));

interestPerBlock = rate->interestPerBlock + rate->interestToHeight;
mnview.EraseInterest(6, id, token_id);
rate = mnview.GetInterestRate(id, token_id);

BOOST_REQUIRE(rate);
BOOST_CHECK_EQUAL(rate->count, 4);
BOOST_CHECK_EQUAL(rate->interestToHeight, interestPerBlock);
BOOST_CHECK_EQUAL(rate->interestPerBlock, netInterest * rate->count / (365 * Params().GetConsensus().blocksPerDay()));

for (int i = 0; i < 4; i++) {
mnview.EraseInterest(6, id, token_id);
}
rate = mnview.GetInterestRate(id, token_id);

BOOST_REQUIRE(!rate);
}

BOOST_AUTO_TEST_SUITE_END()